| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| // Copyright 2022 Dolphin Emulator Project | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| #pragma once | ||
|
|
||
| #include <memory> | ||
| #include <string> | ||
| #include <string_view> | ||
| #include <vector> | ||
|
|
||
| #include <picojson.h> | ||
|
|
||
| #include "VideoCommon/AbstractTexture.h" | ||
| #include "VideoCommon/Assets/CustomAssetLibrary.h" | ||
| #include "VideoCommon/Assets/MaterialAsset.h" | ||
| #include "VideoCommon/Assets/ShaderAsset.h" | ||
| #include "VideoCommon/Assets/TextureAsset.h" | ||
| #include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h" | ||
| #include "VideoCommon/ShaderGenCommon.h" | ||
|
|
||
| class CustomPipelineAction final : public GraphicsModAction | ||
| { | ||
| public: | ||
| struct PipelinePassPassDescription | ||
| { | ||
| std::string m_pixel_material_asset; | ||
| }; | ||
|
|
||
| static std::unique_ptr<CustomPipelineAction> | ||
| Create(const picojson::value& json_data, | ||
| std::shared_ptr<VideoCommon::CustomAssetLibrary> library); | ||
| CustomPipelineAction(std::shared_ptr<VideoCommon::CustomAssetLibrary> library, | ||
| std::vector<PipelinePassPassDescription> pass_descriptions); | ||
| ~CustomPipelineAction(); | ||
| void OnDrawStarted(GraphicsModActionData::DrawStarted*) override; | ||
| void OnTextureCreate(GraphicsModActionData::TextureCreate*) override; | ||
|
|
||
| private: | ||
| std::shared_ptr<VideoCommon::CustomAssetLibrary> m_library; | ||
| std::vector<PipelinePassPassDescription> m_passes_config; | ||
| struct PipelinePass | ||
| { | ||
| VideoCommon::CachedAsset<VideoCommon::MaterialAsset> m_pixel_material; | ||
| VideoCommon::CachedAsset<VideoCommon::PixelShaderAsset> m_pixel_shader; | ||
| std::vector<VideoCommon::CachedAsset<VideoCommon::GameTextureAsset>> m_game_textures; | ||
| }; | ||
| std::vector<PipelinePass> m_passes; | ||
|
|
||
| ShaderCode m_last_generated_shader_code; | ||
|
|
||
| bool m_valid = true; | ||
|
|
||
| std::vector<std::string> m_texture_code_names; | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,376 @@ | ||
| // Copyright 2022 Dolphin Emulator Project | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| #include "VideoCommon/GraphicsModSystem/Runtime/CustomShaderCache.h" | ||
| #include "VideoCommon/AbstractGfx.h" | ||
| #include "VideoCommon/VideoConfig.h" | ||
|
|
||
| CustomShaderCache::CustomShaderCache() | ||
| { | ||
| m_api_type = g_ActiveConfig.backend_info.api_type; | ||
| m_host_config.bits = ShaderHostConfig::GetCurrent().bits; | ||
|
|
||
| m_async_shader_compiler = g_gfx->CreateAsyncShaderCompiler(); | ||
| m_async_shader_compiler->StartWorkerThreads(1); // TODO | ||
|
|
||
| m_async_uber_shader_compiler = g_gfx->CreateAsyncShaderCompiler(); | ||
| m_async_uber_shader_compiler->StartWorkerThreads(1); // TODO | ||
|
|
||
| m_frame_end_handler = | ||
| AfterFrameEvent::Register([this] { RetrieveAsyncShaders(); }, "RetreiveAsyncShaders"); | ||
| } | ||
|
|
||
| CustomShaderCache::~CustomShaderCache() | ||
| { | ||
| if (m_async_shader_compiler) | ||
| m_async_shader_compiler->StopWorkerThreads(); | ||
|
|
||
| if (m_async_uber_shader_compiler) | ||
| m_async_uber_shader_compiler->StopWorkerThreads(); | ||
| } | ||
|
|
||
| void CustomShaderCache::RetrieveAsyncShaders() | ||
| { | ||
| m_async_shader_compiler->RetrieveWorkItems(); | ||
| m_async_uber_shader_compiler->RetrieveWorkItems(); | ||
| } | ||
|
|
||
| void CustomShaderCache::Reload() | ||
| { | ||
| while (m_async_shader_compiler->HasPendingWork() || m_async_shader_compiler->HasCompletedWork()) | ||
| { | ||
| m_async_shader_compiler->RetrieveWorkItems(); | ||
| } | ||
|
|
||
| while (m_async_uber_shader_compiler->HasPendingWork() || | ||
| m_async_uber_shader_compiler->HasCompletedWork()) | ||
| { | ||
| m_async_uber_shader_compiler->RetrieveWorkItems(); | ||
| } | ||
|
|
||
| m_ps_cache = {}; | ||
| m_uber_ps_cache = {}; | ||
| m_pipeline_cache = {}; | ||
| m_uber_pipeline_cache = {}; | ||
| } | ||
|
|
||
| std::optional<const AbstractPipeline*> | ||
| CustomShaderCache::GetPipelineAsync(const VideoCommon::GXPipelineUid& uid, | ||
| const CustomShaderInstance& custom_shaders, | ||
| const AbstractPipelineConfig& pipeline_config) | ||
| { | ||
| if (auto holder = m_pipeline_cache.GetHolder(uid, custom_shaders)) | ||
| { | ||
| if (holder->pending) | ||
| return std::nullopt; | ||
| return holder->value.get(); | ||
| } | ||
| AsyncCreatePipeline(uid, custom_shaders, pipeline_config); | ||
| return std::nullopt; | ||
| } | ||
|
|
||
| std::optional<const AbstractPipeline*> | ||
| CustomShaderCache::GetPipelineAsync(const VideoCommon::GXUberPipelineUid& uid, | ||
| const CustomShaderInstance& custom_shaders, | ||
| const AbstractPipelineConfig& pipeline_config) | ||
| { | ||
| if (auto holder = m_uber_pipeline_cache.GetHolder(uid, custom_shaders)) | ||
| { | ||
| if (holder->pending) | ||
| return std::nullopt; | ||
| return holder->value.get(); | ||
| } | ||
| AsyncCreatePipeline(uid, custom_shaders, pipeline_config); | ||
| return std::nullopt; | ||
| } | ||
|
|
||
| void CustomShaderCache::AsyncCreatePipeline(const VideoCommon::GXPipelineUid& uid, | ||
|
|
||
| const CustomShaderInstance& custom_shaders, | ||
| const AbstractPipelineConfig& pipeline_config) | ||
| { | ||
| class PipelineWorkItem final : public VideoCommon::AsyncShaderCompiler::WorkItem | ||
| { | ||
| public: | ||
| PipelineWorkItem(CustomShaderCache* shader_cache, const VideoCommon::GXPipelineUid& uid, | ||
| const CustomShaderInstance& custom_shaders, PipelineIterator iterator, | ||
| const AbstractPipelineConfig& pipeline_config) | ||
| : m_shader_cache(shader_cache), m_uid(uid), m_iterator(iterator), | ||
| m_custom_shaders(custom_shaders), m_config(pipeline_config) | ||
| { | ||
| SetStagesReady(); | ||
| } | ||
|
|
||
| void SetStagesReady() | ||
| { | ||
| m_stages_ready = true; | ||
|
|
||
| PixelShaderUid ps_uid = m_uid.ps_uid; | ||
| ClearUnusedPixelShaderUidBits(m_shader_cache->m_api_type, m_shader_cache->m_host_config, | ||
| &ps_uid); | ||
|
|
||
| if (auto holder = m_shader_cache->m_ps_cache.GetHolder(ps_uid, m_custom_shaders)) | ||
| { | ||
| // If the pixel shader is no longer pending compilation | ||
| // and the shader compilation succeeded, set | ||
| // the pipeline to use the new pixel shader. | ||
| // Otherwise, use the existing shader. | ||
| if (!holder->pending && holder->value.get()) | ||
| { | ||
| m_config.pixel_shader = holder->value.get(); | ||
| } | ||
| m_stages_ready &= !holder->pending; | ||
| } | ||
| else | ||
| { | ||
| m_stages_ready &= false; | ||
| m_shader_cache->QueuePixelShaderCompile(ps_uid, m_custom_shaders); | ||
| } | ||
| } | ||
|
|
||
| bool Compile() override | ||
| { | ||
| if (m_stages_ready) | ||
| { | ||
| m_pipeline = g_gfx->CreatePipeline(m_config); | ||
| } | ||
| return true; | ||
| } | ||
|
|
||
| void Retrieve() override | ||
| { | ||
| if (m_stages_ready) | ||
| { | ||
| m_shader_cache->NotifyPipelineFinished(m_iterator, std::move(m_pipeline)); | ||
| } | ||
| else | ||
| { | ||
| // Re-queue for next frame. | ||
| auto wi = m_shader_cache->m_async_shader_compiler->CreateWorkItem<PipelineWorkItem>( | ||
| m_shader_cache, m_uid, m_custom_shaders, m_iterator, m_config); | ||
| m_shader_cache->m_async_shader_compiler->QueueWorkItem(std::move(wi), 0); | ||
| } | ||
| } | ||
|
|
||
| private: | ||
| CustomShaderCache* m_shader_cache; | ||
| std::unique_ptr<AbstractPipeline> m_pipeline; | ||
| VideoCommon::GXPipelineUid m_uid; | ||
| PipelineIterator m_iterator; | ||
| AbstractPipelineConfig m_config; | ||
| CustomShaderInstance m_custom_shaders; | ||
| bool m_stages_ready; | ||
| }; | ||
|
|
||
| auto list_iter = m_pipeline_cache.InsertElement(uid, custom_shaders); | ||
| auto work_item = m_async_shader_compiler->CreateWorkItem<PipelineWorkItem>( | ||
| this, uid, custom_shaders, list_iter, pipeline_config); | ||
| m_async_shader_compiler->QueueWorkItem(std::move(work_item), 0); | ||
| } | ||
|
|
||
| void CustomShaderCache::AsyncCreatePipeline(const VideoCommon::GXUberPipelineUid& uid, | ||
|
|
||
| const CustomShaderInstance& custom_shaders, | ||
| const AbstractPipelineConfig& pipeline_config) | ||
| { | ||
| class PipelineWorkItem final : public VideoCommon::AsyncShaderCompiler::WorkItem | ||
| { | ||
| public: | ||
| PipelineWorkItem(CustomShaderCache* shader_cache, const VideoCommon::GXUberPipelineUid& uid, | ||
| const CustomShaderInstance& custom_shaders, UberPipelineIterator iterator, | ||
| const AbstractPipelineConfig& pipeline_config) | ||
| : m_shader_cache(shader_cache), m_uid(uid), m_iterator(iterator), | ||
| m_custom_shaders(custom_shaders), m_config(pipeline_config) | ||
| { | ||
| SetStagesReady(); | ||
| } | ||
|
|
||
| void SetStagesReady() | ||
| { | ||
| m_stages_ready = true; | ||
|
|
||
| UberShader::PixelShaderUid ps_uid = m_uid.ps_uid; | ||
| ClearUnusedPixelShaderUidBits(m_shader_cache->m_api_type, m_shader_cache->m_host_config, | ||
| &ps_uid); | ||
|
|
||
| if (auto holder = m_shader_cache->m_uber_ps_cache.GetHolder(ps_uid, m_custom_shaders)) | ||
| { | ||
| if (!holder->pending && holder->value.get()) | ||
| { | ||
| m_config.pixel_shader = holder->value.get(); | ||
| } | ||
| m_stages_ready &= !holder->pending; | ||
| } | ||
| else | ||
| { | ||
| m_stages_ready &= false; | ||
| m_shader_cache->QueuePixelShaderCompile(ps_uid, m_custom_shaders); | ||
| } | ||
| } | ||
|
|
||
| bool Compile() override | ||
| { | ||
| if (m_stages_ready) | ||
| { | ||
| if (m_config.pixel_shader == nullptr || m_config.vertex_shader == nullptr) | ||
| return false; | ||
|
|
||
| m_pipeline = g_gfx->CreatePipeline(m_config); | ||
| } | ||
| return true; | ||
| } | ||
|
|
||
| void Retrieve() override | ||
| { | ||
| if (m_stages_ready) | ||
| { | ||
| m_shader_cache->NotifyPipelineFinished(m_iterator, std::move(m_pipeline)); | ||
| } | ||
| else | ||
| { | ||
| // Re-queue for next frame. | ||
| auto wi = m_shader_cache->m_async_uber_shader_compiler->CreateWorkItem<PipelineWorkItem>( | ||
| m_shader_cache, m_uid, m_custom_shaders, m_iterator, m_config); | ||
| m_shader_cache->m_async_uber_shader_compiler->QueueWorkItem(std::move(wi), 0); | ||
| } | ||
| } | ||
|
|
||
| private: | ||
| CustomShaderCache* m_shader_cache; | ||
| std::unique_ptr<AbstractPipeline> m_pipeline; | ||
| VideoCommon::GXUberPipelineUid m_uid; | ||
| UberPipelineIterator m_iterator; | ||
| AbstractPipelineConfig m_config; | ||
| CustomShaderInstance m_custom_shaders; | ||
| bool m_stages_ready; | ||
| }; | ||
|
|
||
| auto list_iter = m_uber_pipeline_cache.InsertElement(uid, custom_shaders); | ||
| auto work_item = m_async_uber_shader_compiler->CreateWorkItem<PipelineWorkItem>( | ||
| this, uid, custom_shaders, list_iter, pipeline_config); | ||
| m_async_uber_shader_compiler->QueueWorkItem(std::move(work_item), 0); | ||
| } | ||
|
|
||
| void CustomShaderCache::NotifyPipelineFinished(PipelineIterator iterator, | ||
| std::unique_ptr<AbstractPipeline> pipeline) | ||
| { | ||
| iterator->second.pending = false; | ||
| iterator->second.value = std::move(pipeline); | ||
| } | ||
|
|
||
| void CustomShaderCache::NotifyPipelineFinished(UberPipelineIterator iterator, | ||
| std::unique_ptr<AbstractPipeline> pipeline) | ||
| { | ||
| iterator->second.pending = false; | ||
| iterator->second.value = std::move(pipeline); | ||
| } | ||
|
|
||
| void CustomShaderCache::QueuePixelShaderCompile(const PixelShaderUid& uid, | ||
|
|
||
| const CustomShaderInstance& custom_shaders) | ||
| { | ||
| class PixelShaderWorkItem final : public VideoCommon::AsyncShaderCompiler::WorkItem | ||
| { | ||
| public: | ||
| PixelShaderWorkItem(CustomShaderCache* shader_cache, const PixelShaderUid& uid, | ||
| const CustomShaderInstance& custom_shaders, PixelShaderIterator iter) | ||
| : m_shader_cache(shader_cache), m_uid(uid), m_custom_shaders(custom_shaders), m_iter(iter) | ||
| { | ||
| } | ||
|
|
||
| bool Compile() override | ||
| { | ||
| m_shader = m_shader_cache->CompilePixelShader(m_uid, m_custom_shaders); | ||
| return true; | ||
| } | ||
|
|
||
| void Retrieve() override | ||
| { | ||
| m_shader_cache->NotifyPixelShaderFinished(m_iter, std::move(m_shader)); | ||
| } | ||
|
|
||
| private: | ||
| CustomShaderCache* m_shader_cache; | ||
| std::unique_ptr<AbstractShader> m_shader; | ||
| PixelShaderUid m_uid; | ||
| CustomShaderInstance m_custom_shaders; | ||
| PixelShaderIterator m_iter; | ||
| }; | ||
|
|
||
| auto list_iter = m_ps_cache.InsertElement(uid, custom_shaders); | ||
| auto work_item = m_async_shader_compiler->CreateWorkItem<PixelShaderWorkItem>( | ||
| this, uid, custom_shaders, list_iter); | ||
| m_async_shader_compiler->QueueWorkItem(std::move(work_item), 0); | ||
| } | ||
|
|
||
| void CustomShaderCache::QueuePixelShaderCompile(const UberShader::PixelShaderUid& uid, | ||
|
|
||
| const CustomShaderInstance& custom_shaders) | ||
| { | ||
| class PixelShaderWorkItem final : public VideoCommon::AsyncShaderCompiler::WorkItem | ||
| { | ||
| public: | ||
| PixelShaderWorkItem(CustomShaderCache* shader_cache, const UberShader::PixelShaderUid& uid, | ||
| const CustomShaderInstance& custom_shaders, UberPixelShaderIterator iter) | ||
| : m_shader_cache(shader_cache), m_uid(uid), m_custom_shaders(custom_shaders), m_iter(iter) | ||
| { | ||
| } | ||
|
|
||
| bool Compile() override | ||
| { | ||
| m_shader = m_shader_cache->CompilePixelShader(m_uid, m_custom_shaders); | ||
| return true; | ||
| } | ||
|
|
||
| void Retrieve() override | ||
| { | ||
| m_shader_cache->NotifyPixelShaderFinished(m_iter, std::move(m_shader)); | ||
| } | ||
|
|
||
| private: | ||
| CustomShaderCache* m_shader_cache; | ||
| std::unique_ptr<AbstractShader> m_shader; | ||
| UberShader::PixelShaderUid m_uid; | ||
| CustomShaderInstance m_custom_shaders; | ||
| UberPixelShaderIterator m_iter; | ||
| }; | ||
|
|
||
| auto list_iter = m_uber_ps_cache.InsertElement(uid, custom_shaders); | ||
| auto work_item = m_async_uber_shader_compiler->CreateWorkItem<PixelShaderWorkItem>( | ||
| this, uid, custom_shaders, list_iter); | ||
| m_async_uber_shader_compiler->QueueWorkItem(std::move(work_item), 0); | ||
| } | ||
|
|
||
| std::unique_ptr<AbstractShader> | ||
| CustomShaderCache::CompilePixelShader(const PixelShaderUid& uid, | ||
| const CustomShaderInstance& custom_shaders) const | ||
| { | ||
| const ShaderCode source_code = GeneratePixelShaderCode( | ||
| m_api_type, m_host_config, uid.GetUidData(), custom_shaders.pixel_contents); | ||
| return g_gfx->CreateShaderFromSource(ShaderStage::Pixel, source_code.GetBuffer(), | ||
| "Custom Pixel Shader"); | ||
| } | ||
|
|
||
| std::unique_ptr<AbstractShader> | ||
| CustomShaderCache::CompilePixelShader(const UberShader::PixelShaderUid& uid, | ||
| const CustomShaderInstance& custom_shaders) const | ||
| { | ||
| const ShaderCode source_code = | ||
| GenPixelShader(m_api_type, m_host_config, uid.GetUidData(), custom_shaders.pixel_contents); | ||
| return g_gfx->CreateShaderFromSource(ShaderStage::Pixel, source_code.GetBuffer(), | ||
| "Custom Uber Pixel Shader"); | ||
| } | ||
|
|
||
| void CustomShaderCache::NotifyPixelShaderFinished(PixelShaderIterator iterator, | ||
| std::unique_ptr<AbstractShader> shader) | ||
| { | ||
| iterator->second.pending = false; | ||
| iterator->second.value = std::move(shader); | ||
| } | ||
|
|
||
| void CustomShaderCache::NotifyPixelShaderFinished(UberPixelShaderIterator iterator, | ||
| std::unique_ptr<AbstractShader> shader) | ||
| { | ||
| iterator->second.pending = false; | ||
| iterator->second.value = std::move(shader); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,144 @@ | ||
| // Copyright 2022 Dolphin Emulator Project | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| #pragma once | ||
|
|
||
| #include <array> | ||
| #include <list> | ||
| #include <map> | ||
| #include <memory> | ||
| #include <optional> | ||
| #include <string> | ||
| #include <string_view> | ||
|
|
||
| #include "VideoCommon/AbstractPipeline.h" | ||
| #include "VideoCommon/AbstractShader.h" | ||
| #include "VideoCommon/AsyncShaderCompiler.h" | ||
| #include "VideoCommon/GXPipelineTypes.h" | ||
| #include "VideoCommon/PixelShaderGen.h" | ||
| #include "VideoCommon/ShaderGenCommon.h" | ||
| #include "VideoCommon/UberShaderPixel.h" | ||
| #include "VideoCommon/VideoEvents.h" | ||
|
|
||
| struct CustomShaderInstance | ||
| { | ||
| CustomPixelShaderContents pixel_contents; | ||
|
|
||
| bool operator==(const CustomShaderInstance& other) const = default; | ||
| }; | ||
|
|
||
| class CustomShaderCache | ||
| { | ||
| public: | ||
| CustomShaderCache(); | ||
| ~CustomShaderCache(); | ||
| CustomShaderCache(const CustomShaderCache&) = delete; | ||
| CustomShaderCache(CustomShaderCache&&) = delete; | ||
| CustomShaderCache& operator=(const CustomShaderCache&) = delete; | ||
| CustomShaderCache& operator=(CustomShaderCache&&) = delete; | ||
|
|
||
| // Changes the shader host config. Shaders should be reloaded afterwards. | ||
| void SetHostConfig(const ShaderHostConfig& host_config) { m_host_config.bits = host_config.bits; } | ||
|
|
||
| // Retrieves all pending shaders/pipelines from the async compiler. | ||
| void RetrieveAsyncShaders(); | ||
|
|
||
| // Reloads/recreates all shaders and pipelines. | ||
| void Reload(); | ||
|
|
||
| // The optional will be empty if this pipeline is now background compiling. | ||
| std::optional<const AbstractPipeline*> | ||
| GetPipelineAsync(const VideoCommon::GXPipelineUid& uid, | ||
| const CustomShaderInstance& custom_shaders, | ||
| const AbstractPipelineConfig& pipeline_config); | ||
| std::optional<const AbstractPipeline*> | ||
| GetPipelineAsync(const VideoCommon::GXUberPipelineUid& uid, | ||
| const CustomShaderInstance& custom_shaders, | ||
| const AbstractPipelineConfig& pipeline_config); | ||
|
|
||
| private: | ||
| // Configuration bits. | ||
| APIType m_api_type = APIType::Nothing; | ||
| ShaderHostConfig m_host_config = {}; | ||
| std::unique_ptr<VideoCommon::AsyncShaderCompiler> m_async_shader_compiler; | ||
| std::unique_ptr<VideoCommon::AsyncShaderCompiler> m_async_uber_shader_compiler; | ||
|
|
||
| void AsyncCreatePipeline(const VideoCommon::GXPipelineUid& uid, | ||
| const CustomShaderInstance& custom_shaders, | ||
| const AbstractPipelineConfig& pipeline_config); | ||
| void AsyncCreatePipeline(const VideoCommon::GXUberPipelineUid& uid, | ||
| const CustomShaderInstance& custom_shaders, | ||
| const AbstractPipelineConfig& pipeline_config); | ||
|
|
||
| // Shader/Pipeline cache helper | ||
| template <typename Uid, typename ValueType> | ||
| struct Cache | ||
| { | ||
| struct CacheHolder | ||
| { | ||
| std::unique_ptr<ValueType> value = nullptr; | ||
| bool pending = true; | ||
| }; | ||
| using CacheElement = std::pair<CustomShaderInstance, CacheHolder>; | ||
| using CacheList = std::list<CacheElement>; | ||
| std::map<Uid, CacheList> uid_to_cachelist; | ||
|
|
||
| const CacheHolder* GetHolder(const Uid& uid, const CustomShaderInstance& custom_shaders) const | ||
| { | ||
| if (auto uuid_it = uid_to_cachelist.find(uid); uuid_it != uid_to_cachelist.end()) | ||
| { | ||
| for (const auto& [custom_shader_val, holder] : uuid_it->second) | ||
| { | ||
| if (custom_shaders == custom_shader_val) | ||
| { | ||
| return &holder; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return nullptr; | ||
| } | ||
|
|
||
| typename CacheList::iterator InsertElement(const Uid& uid, | ||
| const CustomShaderInstance& custom_shaders) | ||
| { | ||
| CacheList& cachelist = uid_to_cachelist[uid]; | ||
| CacheElement e{custom_shaders, CacheHolder{}}; | ||
| return cachelist.emplace(cachelist.begin(), std::move(e)); | ||
| } | ||
| }; | ||
|
|
||
| Cache<PixelShaderUid, AbstractShader> m_ps_cache; | ||
| Cache<UberShader::PixelShaderUid, AbstractShader> m_uber_ps_cache; | ||
| Cache<VideoCommon::GXPipelineUid, AbstractPipeline> m_pipeline_cache; | ||
| Cache<VideoCommon::GXUberPipelineUid, AbstractPipeline> m_uber_pipeline_cache; | ||
|
|
||
| using PipelineIterator = Cache<VideoCommon::GXPipelineUid, AbstractPipeline>::CacheList::iterator; | ||
| using UberPipelineIterator = | ||
| Cache<VideoCommon::GXUberPipelineUid, AbstractPipeline>::CacheList::iterator; | ||
| using PixelShaderIterator = Cache<PixelShaderUid, AbstractShader>::CacheList::iterator; | ||
| using UberPixelShaderIterator = | ||
| Cache<UberShader::PixelShaderUid, AbstractShader>::CacheList::iterator; | ||
|
|
||
| void NotifyPipelineFinished(PipelineIterator iterator, | ||
| std::unique_ptr<AbstractPipeline> pipeline); | ||
| void NotifyPipelineFinished(UberPipelineIterator iterator, | ||
| std::unique_ptr<AbstractPipeline> pipeline); | ||
|
|
||
| std::unique_ptr<AbstractShader> | ||
| CompilePixelShader(const PixelShaderUid& uid, const CustomShaderInstance& custom_shaders) const; | ||
| void NotifyPixelShaderFinished(PixelShaderIterator iterator, | ||
| std::unique_ptr<AbstractShader> shader); | ||
| std::unique_ptr<AbstractShader> | ||
| CompilePixelShader(const UberShader::PixelShaderUid& uid, | ||
| const CustomShaderInstance& custom_shaders) const; | ||
| void NotifyPixelShaderFinished(UberPixelShaderIterator iterator, | ||
| std::unique_ptr<AbstractShader> shader); | ||
|
|
||
| void QueuePixelShaderCompile(const PixelShaderUid& uid, | ||
| const CustomShaderInstance& custom_shaders); | ||
| void QueuePixelShaderCompile(const UberShader::PixelShaderUid& uid, | ||
| const CustomShaderInstance& custom_shaders); | ||
|
|
||
| Common::EventHook m_frame_end_handler; | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,251 @@ | ||
| # Dolphin Custom Pipeline Specification | ||
|
|
||
| Dolphin provides content creators a way to overwrite its internal graphics pipeline data using graphics mods. At the moment, this supports modifying only the pixel shader. This document will describe the specification and give some examples. | ||
|
|
||
| ## Graphics mod metadata format | ||
|
|
||
| This feature is powered by graphics mods. This document assumes the user is familiar with them and will only detail the action specific data needed to trigger this capability. | ||
|
|
||
| The action type for this feature is `custom_pipeline`. This action has the following data: | ||
|
|
||
| |Identifier |Required | Since | | ||
| |-------------------------|---------|-------| | ||
| |``passes`` | **Yes** | v1 | | ||
|
|
||
| `passes` is an array of pass blobs. Note that at the moment, Dolphin only supports a single pass. Each pass can have the following data: | ||
|
|
||
| |Identifier |Required | Since | | ||
| |-------------------------|---------|-------| | ||
| |``pixel_material_asset`` | **Yes** | v1 | | ||
|
|
||
| Here `pixel_material_asset` is the name of a material asset. | ||
|
|
||
| A full example is given below: | ||
|
|
||
| ```json | ||
| { | ||
| "assets": [ | ||
| { | ||
| "name": "material_replace_normal", | ||
| "data": | ||
| { | ||
| "": "normal.material.json" | ||
| } | ||
| }, | ||
| { | ||
| "name": "shader_replace_normal", | ||
| "data": | ||
| { | ||
| "metadata": "replace_normal.shader.json", | ||
| "shader": "replace_normal.glsl" | ||
| } | ||
| }, | ||
| { | ||
| "name": "normal_texture", | ||
| "data": | ||
| { | ||
| "": "normal_texture.png" | ||
| } | ||
| } | ||
| ], | ||
| "features": [ | ||
| { | ||
| "action": "custom_pipeline", | ||
| "action_data": { | ||
| "passes": [ | ||
| { | ||
| "pixel_material_asset": "material_replace_normal" | ||
| } | ||
| ] | ||
| }, | ||
| "group": "PipelineTarget" | ||
| } | ||
| ], | ||
| "groups": [ | ||
| { | ||
| "name": "PipelineTarget", | ||
| "targets": [ | ||
| { | ||
| "texture_filename": "tex1_512x512_m_afdbe7efg332229e_14", | ||
| "type": "draw_started" | ||
| }, | ||
| { | ||
| "texture_filename": "tex1_512x512_m_afdbe7efg332229e_14", | ||
| "type": "create_texture" | ||
| } | ||
| ] | ||
| } | ||
| ] | ||
| } | ||
| ``` | ||
|
|
||
| ## The shader format | ||
|
|
||
| The shaders are written in GLSL and converted to the target shader that the backend uses internally. The user is expected to provide an entrypoint with the following signature: | ||
|
|
||
| ``` | ||
| vec4 custom_main( in CustomShaderData data ) | ||
| ``` | ||
|
|
||
| `CustomShaderData` encompasses all the data that Dolphin will pass to the user (in addition to the `samp` variable outlined above which is how textures are accessed). It has the following structure: | ||
|
|
||
| |Name | Type | Since | Description | | ||
| |-----------------------------|-------------------------|-------|-----------------------------------------------------------------------------------------------| | ||
| |``position`` | vec3 | v1 | The position of this pixel in _view space_ | | ||
| |``normal`` | vec3 | v1 | The normal of this pixel in _view space_ | | ||
| |``texcoord`` | vec3[] | v1 | An array of texture coordinates, the amount available is specified by ``texcoord_count`` | | ||
| |``texcoord_count`` | uint | v1 | The count of texture coordinates | | ||
| |``texmap_to_texcoord_index`` | uint[] | v1 | An array of texture units to texture coordinate values | | ||
| |``lights_chan0_color`` | CustomShaderLightData[] | v1 | An array of color lights for channel 0, the amount is specified by ``light_chan0_color_count``| | ||
| |``lights_chan0_alpha`` | CustomShaderLightData[] | v1 | An array of alpha lights for channel 0, the amount is specified by ``light_chan0_alpha_count``| | ||
| |``lights_chan1_color`` | CustomShaderLightData[] | v1 | An array of color lights for channel 1, the amount is specified by ``light_chan1_color_count``| | ||
| |``lights_chan1_alpha`` | CustomShaderLightData[] | v1 | An array of alpha lights for channel 1, the amount is specified by ``light_chan1_alpha_count``| | ||
| |``ambient_lighting`` | vec4[] | v1 | An array of ambient lighting values. Count is two, one for each color channel | | ||
| |``base_material`` | vec4[] | v1 | An array of the base material values. Count is two, one for each color channel | | ||
| |``tev_stages`` | CustomShaderTevStage[] | v1 | An array of TEV stages, the amount is specified by ``tev_stage_count`` | | ||
| |``tev_stage_count`` | uint | v1 | The count of TEV stages | | ||
| |``final_color`` | vec4 | v1 | The final color generated by Dolphin after all TEV stages are executed | | ||
| |``time_ms`` | uint | v1 | The time that has passed in milliseconds, since the game was started. Useful for animating | | ||
|
|
||
| `CustomShaderLightData` is used to denote lighting data the game is applying when rendering the specific draw call. It has the following structure: | ||
|
|
||
| |Name | Type | Since | Description | | ||
| |-------------------------|-------------------------|-------|-------------------------------------------------------------------------------------------------| | ||
| |``position`` | vec3 | v1 | The position of the light in _view space_ | | ||
| |``direction`` | vec3 | v1 | The direction in _view space_ the light is pointing (only applicable for point and spot lights) | | ||
| |``color`` | vec3 | v1 | The color of the light | | ||
| |``attenuation_type`` | uint | v1 | The attentuation type of the light. See details below | | ||
| |``cosatt`` | vec4 | v1 | The cos attenuation values used | | ||
| |``distatt`` | vec4 | v1 | The distance attenuation values used | | ||
|
|
||
| The `attenuation_type` is defined as a `uint` but is effecitvely an enumeration. It has the following values: | ||
|
|
||
| |Name | Since | Description | | ||
| |--------------------------------------------------|-------|-------------------------------------------------------------------------| | ||
| |``CUSTOM_SHADER_LIGHTING_ATTENUATION_TYPE_POINT`` | v1 | This value denotes the lighting attentuation is for a point light | | ||
| |``CUSTOM_SHADER_LIGHTING_ATTENUATION_TYPE_DIR`` | v1 | This value denotes the lighting attentuation is for a directional light | | ||
| |``CUSTOM_SHADER_LIGHTING_ATTENUATION_TYPE_SPOT`` | v1 | This value denotes the lighting attentuation is for a directional light | | ||
|
|
||
|
|
||
| `CustomShaderTevStage` is used to denote the various TEV operations. Each operation describes a graphical operation that the game is applying when rendering the specific draw call. It has the following structure: | ||
|
|
||
| |Name | Type | Since | Description | | ||
| |-------------------------|----------------------------------|-------|-------------------------------------------------------------------------------| | ||
| |``input_color`` | CustomShaderTevStageInputColor[] | v1 | The four color inputs that are used to produce the final output of this stage | | ||
| |``input_alpha`` | CustomShaderTevStageInputAlpha[] | v1 | The four alpha inputs that are used to produce the final output of this stage | | ||
| |``texmap`` | uint | v1 | The texture unit for this stage | | ||
| |``output_color`` | vec4 | v1 | The final output color this stage produces | | ||
|
|
||
|
|
||
| `CustomShaderTevStageInputColor` is a single input TEV operation for a color value. It has the following structure: | ||
|
|
||
| |Name | Type | Since | Description | | ||
| |-------------------------|------|-------|-------------------------------------------------| | ||
| |``input_type`` | uint | v1 | The input type of the input. See details below | | ||
| |``value`` | vec3 | v1 | The value of input | | ||
|
|
||
| The `input_type` is defined as a `uint` but is effectively an enumeration. it has the following values: | ||
|
|
||
| |Name | Since | Description | | ||
| |--------------------------------------------------|-------|---------------------------------------------------------------------------| | ||
| |``CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_PREV`` | v1 | The value is provided by the last stage | | ||
| |``CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR`` | v1 | The value is provided by the color data | | ||
| |``CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_TEX`` | v1 | The value is provided by a texture | | ||
| |``CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_RAS`` | v1 | | | ||
| |``CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_KONST`` | v1 | The value is a constant value defined by the software | | ||
| |``CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_NUMERIC`` | v1 | The value is a constant numeric value like vec3(0, 0, 0) or vec3(1, 1, 1) | | ||
|
|
||
| `CustomShaderTevStageInputAlpha` is a single input TEV operation for an alpha value. It has the following structure: | ||
|
|
||
| |Name | Type | Since | Description | | ||
| |-------------------------|------|-------|-------------------------------------------------------------------------------| | ||
| |``input_type`` | uint | v1 | The input type of the input. See `input_type` for color input stages | | ||
| |``value`` | uint | v1 | The value of input | | ||
|
|
||
|
|
||
| ## Examples | ||
|
|
||
| Below are a handful of examples. | ||
|
|
||
| ### Single color | ||
|
|
||
| The following shader displays the color red on the screen: | ||
|
|
||
| ```glsl | ||
| vec4 custom_main( in CustomShaderData data ) | ||
| { | ||
| return vec4(1.0, 0.0, 0.0, 1.0); | ||
| } | ||
| ``` | ||
|
|
||
| ### Normal | ||
|
|
||
| The following shader displays the normal on the screen: | ||
|
|
||
| ```glsl | ||
| vec4 custom_main( in CustomShaderData data ) | ||
| { | ||
| return vec4(data.normal * 0.5 + 0.5, 1); | ||
| } | ||
| ``` | ||
|
|
||
| ### Reading a texture | ||
|
|
||
| The following shader displays the contents of the texture denoted in the shader asset as `MY_TEX`: | ||
|
|
||
| ```glsl | ||
| vec4 custom_main( in CustomShaderData data ) | ||
| { | ||
| return texture(samp[MY_TEX_UNIT], MY_TEX_COORD); | ||
| } | ||
| ``` | ||
|
|
||
| ### Capturing the first texture the game renders with | ||
|
|
||
| The following shader would display the contents of the first texture the game uses, ignoring any other operations. If no stages are available or none exist with a texture it would use the final color of all the staging operations: | ||
|
|
||
| ```glsl | ||
| vec4 custom_main( in CustomShaderData data ) | ||
| { | ||
| vec4 final_color = data.final_color; | ||
| uint texture_set = 0; | ||
| for (uint i = 0; i < data.tev_stage_count; i++) | ||
| { | ||
| // There are 4 color inputs | ||
| for (uint j = 0; j < 4; j++) | ||
| { | ||
| if (data.tev_stages[i].input_color[j].input_type == CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_TEX && texture_set == 0) | ||
| { | ||
| final_color = vec4(data.tev_stages[i].input_color[j].value, 1.0); | ||
| texture_set = 1; | ||
| } | ||
| } | ||
| } | ||
| return final_color; | ||
| } | ||
| ``` | ||
|
|
||
| ### Applying lighting with a point type attenuation | ||
|
|
||
| The following shader would apply the lighting for any point lights used during the draw for channel 0's color lights, using blue as a base color: | ||
|
|
||
| ```glsl | ||
| vec4 custom_main( in CustomShaderData data ) | ||
| { | ||
| float total_diffuse = 0; | ||
| for (int i = 0; i < data.light_chan0_color_count; i++) | ||
| { | ||
| if (data.lights_chan0_color[i].attenuation_type == CUSTOM_SHADER_LIGHTING_ATTENUATION_TYPE_POINT) | ||
| { | ||
| vec3 light_dir = normalize(data.lights_chan0_color[i].position - data.position.xyz); | ||
| float attn = (dot(normal, light_dir) >= 0.0) ? max(0.0, dot(normal, data.lights_chan0_color[i].direction.xyz)) : 0.0; | ||
| vec3 cosAttn = data.lights_chan0_color[i].cosatt.xyz; | ||
| vec3 distAttn = data.lights_chan0_color[i].distatt.xyz; | ||
| attn = max(0.0, dot(cosAttn, vec3(1.0, attn, attn*attn))) / dot(distAttn, vec3(1.0, attn, attn * attn)); | ||
| total_diffuse += attn * max(0.0, dot(normal, light_dir)); | ||
| } | ||
| } | ||
| return vec4(total_diffuse * vec3(0, 0, 1), 1); | ||
| } | ||
| ``` |