From 0da5cf60a82490d38a48152dbc95a553a19d5581 Mon Sep 17 00:00:00 2001 From: iwubcode Date: Sat, 3 Dec 2022 23:54:22 -0600 Subject: [PATCH 01/12] VideoCommon: add custom pixel shader definition and custom shader header to shadergen common as it will be used by both the special and uber shader variant of pixel shaders --- Source/Core/VideoCommon/ShaderGenCommon.cpp | 87 +++++++++++++++++++++ Source/Core/VideoCommon/ShaderGenCommon.h | 19 +++++ 2 files changed, 106 insertions(+) diff --git a/Source/Core/VideoCommon/ShaderGenCommon.cpp b/Source/Core/VideoCommon/ShaderGenCommon.cpp index 234703d7d5..5f2b2b224f 100644 --- a/Source/Core/VideoCommon/ShaderGenCommon.cpp +++ b/Source/Core/VideoCommon/ShaderGenCommon.cpp @@ -10,6 +10,7 @@ #include "Core/ConfigManager.h" #include "VideoCommon/VideoCommon.h" #include "VideoCommon/VideoConfig.h" +#include "VideoCommon/XFMemory.h" ShaderHostConfig ShaderHostConfig::GetCurrent() { @@ -362,3 +363,89 @@ const char* GetInterpolationQualifier(bool msaa, bool ssaa, bool in_glsl_interfa return "sample"; } } + +void WriteCustomShaderStructDef(ShaderCode* out, u32 numtexgens) +{ + // Bump this when there are breaking changes to the API + out->Write("#define CUSTOM_SHADER_API_VERSION 1;\n"); + + // CUSTOM_SHADER_LIGHTING_ATTENUATION_TYPE "enum" values + out->Write("const uint CUSTOM_SHADER_LIGHTING_ATTENUATION_TYPE_NONE = {};\n", + static_cast(AttenuationFunc::None)); + out->Write("const uint CUSTOM_SHADER_LIGHTING_ATTENUATION_TYPE_POINT = {};\n", + static_cast(AttenuationFunc::Spec)); + out->Write("const uint CUSTOM_SHADER_LIGHTING_ATTENUATION_TYPE_DIR = {};\n", + static_cast(AttenuationFunc::Dir)); + out->Write("const uint CUSTOM_SHADER_LIGHTING_ATTENUATION_TYPE_SPOT = {};\n", + static_cast(AttenuationFunc::Spot)); + + out->Write("struct CustomShaderLightData\n"); + out->Write("{{\n"); + out->Write("\tfloat3 position;\n"); + out->Write("\tfloat3 direction;\n"); + out->Write("\tfloat3 color;\n"); + out->Write("\tuint attenuation_type;\n"); + out->Write("\tfloat4 cosatt;\n"); + out->Write("\tfloat4 distatt;\n"); + out->Write("}};\n\n"); + + // CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE "enum" values + out->Write("const uint CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_PREV = 0;\n"); + out->Write("const uint CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR = 1;\n"); + out->Write("const uint CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_TEX = 2;\n"); + out->Write("const uint CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_RAS = 3;\n"); + out->Write("const uint CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_KONST = 4;\n"); + out->Write("const uint CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_NUMERIC = 5;\n"); + out->Write("const uint CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_UNUSED = 6;\n"); + + out->Write("struct CustomShaderTevStageInputColor\n"); + out->Write("{{\n"); + out->Write("\tuint input_type;\n"); + out->Write("\tfloat3 value;\n"); + out->Write("}};\n\n"); + + out->Write("struct CustomShaderTevStageInputAlpha\n"); + out->Write("{{\n"); + out->Write("\tuint input_type;\n"); + out->Write("\tfloat value;\n"); + out->Write("}};\n\n"); + + out->Write("struct CustomShaderTevStage\n"); + out->Write("{{\n"); + out->Write("\tCustomShaderTevStageInputColor[4] input_color;\n"); + out->Write("\tCustomShaderTevStageInputAlpha[4] input_alpha;\n"); + out->Write("\tuint texmap;\n"); + out->Write("\tfloat4 output_color;\n"); + out->Write("}};\n\n"); + + // Custom structure for data we pass to custom shader hooks + out->Write("struct CustomShaderData\n"); + out->Write("{{\n"); + out->Write("\tfloat3 position;\n"); + out->Write("\tfloat3 normal;\n"); + if (numtexgens == 0) + { + // Cheat so shaders compile + out->Write("\tfloat3[1] texcoord;\n"); + } + else + { + out->Write("\tfloat3[{}] texcoord;\n", numtexgens); + } + out->Write("\tuint texcoord_count;\n"); + out->Write("\tuint[8] texmap_to_texcoord_index;\n"); + out->Write("\tCustomShaderLightData[8] lights_chan0_color;\n"); + out->Write("\tCustomShaderLightData[8] lights_chan0_alpha;\n"); + out->Write("\tCustomShaderLightData[8] lights_chan1_color;\n"); + out->Write("\tCustomShaderLightData[8] lights_chan1_alpha;\n"); + out->Write("\tfloat4[2] ambient_lighting;\n"); + out->Write("\tfloat4[2] base_material;\n"); + out->Write("\tuint light_chan0_color_count;\n"); + out->Write("\tuint light_chan0_alpha_count;\n"); + out->Write("\tuint light_chan1_color_count;\n"); + out->Write("\tuint light_chan1_alpha_count;\n"); + out->Write("\tCustomShaderTevStage[16] tev_stages;\n"); + out->Write("\tuint tev_stage_count;\n"); + out->Write("\tfloat4 final_color;\n"); + out->Write("}};\n\n"); +} diff --git a/Source/Core/VideoCommon/ShaderGenCommon.h b/Source/Core/VideoCommon/ShaderGenCommon.h index 7112dd4523..60e4b3cef9 100644 --- a/Source/Core/VideoCommon/ShaderGenCommon.h +++ b/Source/Core/VideoCommon/ShaderGenCommon.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -327,3 +328,21 @@ static const char s_geometry_shader_uniforms[] = "\tfloat4 " I_STEREOPARAMS ";\n "\tfloat4 " I_LINEPTPARAMS ";\n" "\tint4 " I_TEXOFFSET ";\n" "\tuint vs_expand;\n"; + +constexpr std::string_view CUSTOM_PIXELSHADER_COLOR_FUNC = "customShaderColor"; + +struct CustomPixelShader +{ + std::string custom_shader; + + bool operator==(const CustomPixelShader& other) const = default; +}; + +struct CustomPixelShaderContents +{ + std::vector shaders; + + bool operator==(const CustomPixelShaderContents& other) const = default; +}; + +void WriteCustomShaderStructDef(ShaderCode* out, u32 numtexgens); From c3a370839a19545e9fda44a5ce9569b1997f48ef Mon Sep 17 00:00:00 2001 From: iwubcode Date: Fri, 11 Nov 2022 20:20:04 -0600 Subject: [PATCH 02/12] VideoCommon: add helper functions to handle generating custom lighting code for a custom pixel shader --- Source/Core/VideoCommon/LightingShaderGen.cpp | 177 ++++++++++++++++++ Source/Core/VideoCommon/LightingShaderGen.h | 4 + 2 files changed, 181 insertions(+) diff --git a/Source/Core/VideoCommon/LightingShaderGen.cpp b/Source/Core/VideoCommon/LightingShaderGen.cpp index b92e63fe9e..646f7277b5 100644 --- a/Source/Core/VideoCommon/LightingShaderGen.cpp +++ b/Source/Core/VideoCommon/LightingShaderGen.cpp @@ -175,3 +175,180 @@ void GetLightingShaderUid(LightingUidData& uid_data) } } } + +void GenerateCustomLightingHeaderDetails(ShaderCode* out, u32 enablelighting, u32 light_mask) +{ + u32 light_count = 0; + for (u32 j = 0; j < NUM_XF_COLOR_CHANNELS; j++) + { + if ((enablelighting & (1 << j)) != 0) // Color lights + { + for (int i = 0; i < 8; ++i) + { + if ((light_mask & (1 << (i + 8 * j))) != 0) + { + light_count++; + } + } + } + if ((enablelighting & (1 << (j + 2))) != 0) // Alpha lights + { + for (int i = 0; i < 8; ++i) + { + if ((light_mask & (1 << (i + 8 * (j + 2)))) != 0) + { + light_count++; + } + } + } + } + if (light_count > 0) + { + out->Write("\tCustomShaderLightData[{}] light;\n", light_count); + } + else + { + // Cheat so shaders compile + out->Write("\tCustomShaderLightData[1] light;\n", light_count); + } + out->Write("\tint light_count;\n"); +} + +void GenerateCustomLightingImplementation(ShaderCode* out, const LightingUidData& uid_data, + std::string_view in_color_name) +{ + auto generate_lighting = [](ShaderCode* out, const LightingUidData& uid_data, int index, + int litchan_index, u32 channel_index, u32 custom_light_index, + bool alpha) { + const auto attnfunc = + static_cast((uid_data.attnfunc >> (2 * litchan_index)) & 0x3); + + const std::string_view light_type = alpha ? "alpha" : "color"; + const std::string name = fmt::format("lights_chan{}_{}", channel_index, light_type); + + out->Write("\t{{\n"); + out->Write("\t\tcustom_data.{}[{}].direction = " LIGHT_DIR ".xyz;\n", name, custom_light_index, + LIGHT_DIR_PARAMS(index)); + out->Write("\t\tcustom_data.{}[{}].position = " LIGHT_POS ".xyz;\n", name, custom_light_index, + LIGHT_POS_PARAMS(index)); + out->Write("\t\tcustom_data.{}[{}].cosatt = " LIGHT_COSATT ";\n", name, custom_light_index, + LIGHT_COSATT_PARAMS(index)); + out->Write("\t\tcustom_data.{}[{}].distatt = " LIGHT_DISTATT ";\n", name, custom_light_index, + LIGHT_DISTATT_PARAMS(index)); + out->Write("\t\tcustom_data.{}[{}].attenuation_type = {};\n", name, custom_light_index, + static_cast(attnfunc)); + if (alpha) + { + out->Write("\t\tcustom_data.{}[{}].color = float3(" LIGHT_COL + ") / float3(255.0, 255.0, 255.0);\n", + name, custom_light_index, LIGHT_COL_PARAMS(index, alpha ? "a" : "rgb")); + } + else + { + out->Write("\t\tcustom_data.{}[{}].color = " LIGHT_COL " / float3(255.0, 255.0, 255.0);\n", + name, custom_light_index, LIGHT_COL_PARAMS(index, alpha ? "a" : "rgb")); + } + out->Write("\t}}\n"); + }; + + for (u32 i = 0; i < 8; i++) + { + for (u32 channel_index = 0; channel_index < NUM_XF_COLOR_CHANNELS; channel_index++) + { + out->Write("\tcustom_data.lights_chan{}_color[{}].direction = float3(0, 0, 0);\n", + channel_index, i); + out->Write("\tcustom_data.lights_chan{}_color[{}].position = float3(0, 0, 0);\n", + channel_index, i); + out->Write("\tcustom_data.lights_chan{}_color[{}].color = float3(0, 0, 0);\n", channel_index, + i); + out->Write("\tcustom_data.lights_chan{}_color[{}].cosatt = float4(0, 0, 0, 0);\n", + channel_index, i); + out->Write("\tcustom_data.lights_chan{}_color[{}].distatt = float4(0, 0, 0, 0);\n", + channel_index, i); + out->Write("\tcustom_data.lights_chan{}_color[{}].attenuation_type = 0;\n", channel_index, i); + + out->Write("\tcustom_data.lights_chan{}_alpha[{}].direction = float3(0, 0, 0);\n", + channel_index, i); + out->Write("\tcustom_data.lights_chan{}_alpha[{}].position = float3(0, 0, 0);\n", + channel_index, i); + out->Write("\tcustom_data.lights_chan{}_alpha[{}].color = float3(0, 0, 0);\n", channel_index, + i); + out->Write("\tcustom_data.lights_chan{}_alpha[{}].cosatt = float4(0, 0, 0, 0);\n", + channel_index, i); + out->Write("\tcustom_data.lights_chan{}_alpha[{}].distatt = float4(0, 0, 0, 0);\n", + channel_index, i); + out->Write("\tcustom_data.lights_chan{}_alpha[{}].attenuation_type = 0;\n", channel_index, i); + } + } + + for (u32 j = 0; j < NUM_XF_COLOR_CHANNELS; j++) + { + const bool colormatsource = !!(uid_data.matsource & (1 << j)); + if (colormatsource) // from vertex + out->Write("custom_data.base_material[{}] = {}{};\n", j, in_color_name, j); + else // from color + out->Write("custom_data.base_material[{}] = {}[{}] / 255.0;\n", j, I_MATERIALS, j + 2); + + if ((uid_data.enablelighting & (1 << j)) != 0) + { + if ((uid_data.ambsource & (1 << j)) != 0) // from vertex + out->Write("custom_data.ambient_lighting[{}] = {}{};\n", j, in_color_name, j); + else // from color + out->Write("custom_data.ambient_lighting[{}] = {}[{}] / 255.0;\n", j, I_MATERIALS, j); + } + else + { + out->Write("custom_data.ambient_lighting[{}] = float4(1, 1, 1, 1);\n", j); + } + + // check if alpha is different + const bool alphamatsource = !!(uid_data.matsource & (1 << (j + 2))); + if (alphamatsource != colormatsource) + { + if (alphamatsource) // from vertex + out->Write("custom_data.base_material[{}].w = {}{}.w;\n", j, in_color_name, j); + else // from color + out->Write("custom_data.base_material[{}].w = {}[{}].w / 255.0;\n", j, I_MATERIALS, j + 2); + } + + if ((uid_data.enablelighting & (1 << (j + 2))) != 0) + { + if ((uid_data.ambsource & (1 << (j + 2))) != 0) // from vertex + out->Write("custom_data.ambient_lighting[{}].w = {}{}.w;\n", j, in_color_name, j); + else // from color + out->Write("custom_data.ambient_lighting[{}].w = {}[{}].w / 255.0;\n", j, I_MATERIALS, j); + } + else + { + out->Write("custom_data.ambient_lighting[{}].w = 1;\n", j); + } + + u32 light_count = 0; + if ((uid_data.enablelighting & (1 << j)) != 0) // Color lights + { + for (int i = 0; i < 8; ++i) + { + if ((uid_data.light_mask & (1 << (i + 8 * j))) != 0) + { + generate_lighting(out, uid_data, i, j, j, light_count, false); + light_count++; + } + } + } + out->Write("\tcustom_data.light_chan{}_color_count = {};\n", j, light_count); + + light_count = 0; + if ((uid_data.enablelighting & (1 << (j + 2))) != 0) // Alpha lights + { + for (int i = 0; i < 8; ++i) + { + if ((uid_data.light_mask & (1 << (i + 8 * (j + 2)))) != 0) + { + generate_lighting(out, uid_data, i, j + 2, j, light_count, true); + light_count++; + } + } + } + out->Write("\tcustom_data.light_chan{}_alpha_count = {};\n", j, light_count); + } +} diff --git a/Source/Core/VideoCommon/LightingShaderGen.h b/Source/Core/VideoCommon/LightingShaderGen.h index a34c04df74..b06ec40c4a 100644 --- a/Source/Core/VideoCommon/LightingShaderGen.h +++ b/Source/Core/VideoCommon/LightingShaderGen.h @@ -47,3 +47,7 @@ constexpr char s_lighting_struct[] = "struct Light {\n" void GenerateLightingShaderCode(ShaderCode& object, const LightingUidData& uid_data, std::string_view in_color_name, std::string_view dest); void GetLightingShaderUid(LightingUidData& uid_data); + +void GenerateCustomLightingHeaderDetails(ShaderCode* out, u32 enablelighting, u32 light_mask); +void GenerateCustomLightingImplementation(ShaderCode* out, const LightingUidData& uid_data, + std::string_view in_color_name); From e704385fce2a6383891d50253aafb61a486e7981 Mon Sep 17 00:00:00 2001 From: iwubcode Date: Sat, 30 Jul 2022 00:33:29 -0500 Subject: [PATCH 03/12] VideoCommon: pixel shader gen changes needed to support custom pixel shaders in graphics mods --- Source/Core/VideoCommon/PixelShaderGen.cpp | 233 ++++++++++++++++++++- Source/Core/VideoCommon/PixelShaderGen.h | 6 +- Source/Core/VideoCommon/ShaderCache.cpp | 2 +- 3 files changed, 234 insertions(+), 7 deletions(-) diff --git a/Source/Core/VideoCommon/PixelShaderGen.cpp b/Source/Core/VideoCommon/PixelShaderGen.cpp index 0b96e49f4a..326cd975d9 100644 --- a/Source/Core/VideoCommon/PixelShaderGen.cpp +++ b/Source/Core/VideoCommon/PixelShaderGen.cpp @@ -3,6 +3,7 @@ #include "VideoCommon/PixelShaderGen.h" +#include #include #include #include @@ -130,6 +131,17 @@ constexpr Common::EnumMap tev_c_input_table{ "int3(0,0,0)", // ZERO }; +constexpr Common::EnumMap tev_c_input_type{ + "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_PREV", "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_PREV", + "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR", "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR", + "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR", "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR", + "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR", "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR", + "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_TEX", "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_TEX", + "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_RAS", "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_RAS", + "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_NUMERIC", "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_NUMERIC", + "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_KONST", "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_NUMERIC", +}; + constexpr Common::EnumMap tev_a_input_table{ "prev.a", // APREV, "c0.a", // A0, @@ -141,6 +153,13 @@ constexpr Common::EnumMap tev_a_input_table{ "0", // ZERO }; +constexpr Common::EnumMap tev_a_input_type{ + "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_PREV", "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR", + "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR", "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR", + "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_TEX", "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_RAS", + "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_KONST", "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_NUMERIC", +}; + constexpr Common::EnumMap tev_ras_table{ "iround(col0 * 255.0)", "iround(col1 * 255.0)", @@ -732,8 +751,128 @@ uint WrapCoord(int coord, uint wrap, int size) {{ } } +void WriteCustomShaderStructImpl(ShaderCode* out, u32 num_stages, bool per_pixel_lighting, + const pixel_shader_uid_data* uid_data) +{ + out->Write("\tCustomShaderData custom_data;\n"); + + if (per_pixel_lighting) + { + out->Write("\tcustom_data.position = WorldPos;\n"); + out->Write("\tcustom_data.normal = Normal;\n"); + } + else + { + out->Write("\tcustom_data.position = float3(0, 0, 0);\n"); + out->Write("\tcustom_data.normal = float3(0, 0, 0);\n"); + } + + if (uid_data->genMode_numtexgens == 0) [[unlikely]] + { + out->Write("\tcustom_data.texcoord[0] = float3(0, 0, 0);\n"); + } + else + { + for (u32 i = 0; i < uid_data->genMode_numtexgens; ++i) + { + out->Write("\tif (tex{0}.z == 0.0)\n", i); + out->Write("\t{{\n"); + out->Write("\t\tcustom_data.texcoord[{0}] = tex{0};\n", i); + out->Write("\t}}\n"); + out->Write("\telse {{\n"); + out->Write("\t\tcustom_data.texcoord[{0}] = float3(tex{0}.xy / tex{0}.z, 0);\n", i); + out->Write("\t}}\n"); + } + } + + for (u32 i = 0; i < 8; i++) + { + // Shader compilation complains if every index isn't initialized + out->Write("\tcustom_data.texmap_to_texcoord_index[{0}] = 0;\n", i); + } + + for (u32 i = 0; i < uid_data->genMode_numindstages; ++i) + { + if ((uid_data->nIndirectStagesUsed & (1U << i)) != 0) + { + u32 texcoord = uid_data->GetTevindirefCoord(i); + const u32 texmap = uid_data->GetTevindirefMap(i); + + // Quirk: when the tex coord is not less than the number of tex gens (i.e. the tex coord does + // not exist), then tex coord 0 is used (though sometimes glitchy effects happen on console). + // This affects the Mario portrait in Luigi's Mansion, where the developers forgot to set + // the number of tex gens to 2 (bug 11462). + if (texcoord >= uid_data->genMode_numtexgens) + texcoord = 0; + + out->Write("\tcustom_data.texmap_to_texcoord_index[{}] = {};\n", texmap, texcoord); + } + } + out->Write("\tcustom_data.texcoord_count = {};\n", uid_data->genMode_numtexgens); + + // Try and do a best guess on what the texcoord index is + // Note: one issue with this would be textures that are used + // multiple times in the same draw but with different texture coordinates. + // In that scenario, only the last texture coordinate would be defined. + // This issue can be seen in how Rogue Squadron 2 does bump mapping + for (u32 i = 0; i < num_stages; i++) + { + auto& tevstage = uid_data->stagehash[i]; + // Quirk: when the tex coord is not less than the number of tex gens (i.e. the tex coord does + // not exist), then tex coord 0 is used (though sometimes glitchy effects happen on console). + u32 texcoord = tevstage.tevorders_texcoord; + const bool has_tex_coord = texcoord < uid_data->genMode_numtexgens; + if (!has_tex_coord) + texcoord = 0; + + out->Write("\tcustom_data.texmap_to_texcoord_index[{}] = {};\n", tevstage.tevorders_texmap, + texcoord); + } + + GenerateCustomLightingImplementation(out, uid_data->lighting, "colors_"); + + for (u32 i = 0; i < 16; i++) + { + // Shader compilation complains if every struct isn't initialized + + // Color Input + for (u32 j = 0; j < 4; j++) + { + out->Write("\tcustom_data.tev_stages[{}].input_color[{}].input_type = " + "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_UNUSED;\n", + i, j); + out->Write("\tcustom_data.tev_stages[{}].input_color[{}].value = " + "float3(0, 0, 0);\n", + i, j); + } + + // Alpha Input + for (u32 j = 0; j < 4; j++) + { + out->Write("\tcustom_data.tev_stages[{}].input_alpha[{}].input_type = " + "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_UNUSED;\n", + i, j); + out->Write("\tcustom_data.tev_stages[{}].input_alpha[{}].value = " + "float(0);\n", + i, j); + } + + // Texmap + out->Write("\tcustom_data.tev_stages[{}].texmap = 0u;\n", i); + + // Output + out->Write("\tcustom_data.tev_stages[{}].output_color = " + "float4(0, 0, 0, 0);\n", + i); + } + + // Actual data will be filled out in the tev stage code, just set the + // stage count for now + out->Write("\tcustom_data.tev_stage_count = {};\n", num_stages); +} + static void WriteStage(ShaderCode& out, const pixel_shader_uid_data* uid_data, int n, - APIType api_type, bool stereo); + APIType api_type, bool stereo, bool has_custom_shaders); static void WriteTevRegular(ShaderCode& out, std::string_view components, TevBias bias, TevOp op, bool clamp, TevScale scale); static void WriteAlphaTest(ShaderCode& out, const pixel_shader_uid_data* uid_data, APIType api_type, @@ -746,7 +885,8 @@ static void WriteColor(ShaderCode& out, APIType api_type, const pixel_shader_uid static void WriteBlend(ShaderCode& out, const pixel_shader_uid_data* uid_data); ShaderCode GeneratePixelShaderCode(APIType api_type, const ShaderHostConfig& host_config, - const pixel_shader_uid_data* uid_data) + const pixel_shader_uid_data* uid_data, + const CustomPixelShaderContents& custom_details) { ShaderCode out; @@ -762,8 +902,17 @@ ShaderCode GeneratePixelShaderCode(APIType api_type, const ShaderHostConfig& hos // Stuff that is shared between ubershaders and pixelgen. WriteBitfieldExtractHeader(out, api_type, host_config); + WritePixelShaderCommonHeader(out, api_type, host_config, uid_data->bounding_box); + // Custom shader details + WriteCustomShaderStructDef(&out, uid_data->genMode_numtexgens); + for (std::size_t i = 0; i < custom_details.shaders.size(); i++) + { + const auto& shader_details = custom_details.shaders[i]; + out.Write(fmt::runtime(shader_details.custom_shader), i); + } + out.Write("\n#define sampleTextureWrapper(texmap, uv, layer) " "sampleTexture(texmap, samp[texmap], uv, layer)\n"); @@ -892,6 +1041,14 @@ ShaderCode GeneratePixelShaderCode(APIType api_type, const ShaderHostConfig& hos out.Write("void main()\n{{\n"); out.Write("\tfloat4 rawpos = gl_FragCoord;\n"); + bool has_custom_shaders = false; + if (std::any_of(custom_details.shaders.begin(), custom_details.shaders.end(), + [](const std::optional& ps) { return ps.has_value(); })) + { + WriteCustomShaderStructImpl(&out, numStages, per_pixel_lighting, uid_data); + has_custom_shaders = true; + } + if (use_framebuffer_fetch) { // Store off a copy of the initial framebuffer value. @@ -1013,7 +1170,7 @@ ShaderCode GeneratePixelShaderCode(APIType api_type, const ShaderHostConfig& hos for (u32 i = 0; i < numStages; i++) { // Build the equation for this stage - WriteStage(out, uid_data, i, api_type, stereo); + WriteStage(out, uid_data, i, api_type, stereo, has_custom_shaders); } { @@ -1146,7 +1303,21 @@ ShaderCode GeneratePixelShaderCode(APIType api_type, const ShaderHostConfig& hos // Write the color and alpha values to the framebuffer // If using shader blend, we still use the separate alpha - WriteColor(out, api_type, uid_data, !uid_data->no_dual_src || uid_data->blend_enable); + const bool use_dual_source = !uid_data->no_dual_src || uid_data->blend_enable; + WriteColor(out, api_type, uid_data, use_dual_source); + + for (std::size_t i = 0; i < custom_details.shaders.size(); i++) + { + const auto& shader_details = custom_details.shaders[i]; + + if (!shader_details.custom_shader.empty()) + { + out.Write("\t{{\n"); + out.Write("\t\tcustom_data.final_color = ocol0;\n"); + out.Write("\t\tocol0.xyz = {}_{}(custom_data).xyz;\n", CUSTOM_PIXELSHADER_COLOR_FUNC, i); + out.Write("\t}}\n\n"); + } + } if (uid_data->blend_enable) WriteBlend(out, uid_data); @@ -1162,7 +1333,7 @@ ShaderCode GeneratePixelShaderCode(APIType api_type, const ShaderHostConfig& hos } static void WriteStage(ShaderCode& out, const pixel_shader_uid_data* uid_data, int n, - APIType api_type, bool stereo) + APIType api_type, bool stereo, bool has_custom_shaders) { using Common::EnumMap; @@ -1556,6 +1727,58 @@ static void WriteStage(ShaderCode& out, const pixel_shader_uid_data* uid_data, i out.Write(", -1024, 1023)"); out.Write(";\n"); + + if (has_custom_shaders) + { + // Color input + out.Write( + "\tcustom_data.tev_stages[{}].input_color[0].value = {} / float3(255.0, 255.0, 255.0);\n", + n, tev_c_input_table[cc.a]); + out.Write("\tcustom_data.tev_stages[{}].input_color[0].input_type = {};\n", n, + tev_c_input_type[cc.a]); + out.Write( + "\tcustom_data.tev_stages[{}].input_color[1].value = {} / float3(255.0, 255.0, 255.0);\n", + n, tev_c_input_table[cc.b]); + out.Write("\tcustom_data.tev_stages[{}].input_color[1].input_type = {};\n", n, + tev_c_input_type[cc.b]); + out.Write( + "\tcustom_data.tev_stages[{}].input_color[2].value = {} / float3(255.0, 255.0, 255.0);\n", + n, tev_c_input_table[cc.c]); + out.Write("\tcustom_data.tev_stages[{}].input_color[2].input_type = {};\n", n, + tev_c_input_type[cc.c]); + out.Write( + "\tcustom_data.tev_stages[{}].input_color[3].value = {} / float3(255.0, 255.0, 255.0);\n", + n, tev_c_input_table[cc.d]); + out.Write("\tcustom_data.tev_stages[{}].input_color[3].input_type = {};\n", n, + tev_c_input_type[cc.d]); + + // Alpha input + out.Write("\tcustom_data.tev_stages[{}].input_alpha[0].value = {} / float(255.0);\n", n, + tev_a_input_table[ac.a]); + out.Write("\tcustom_data.tev_stages[{}].input_alpha[0].input_type = {};\n", n, + tev_a_input_type[ac.a]); + out.Write("\tcustom_data.tev_stages[{}].input_alpha[1].value = {} / float(255.0);\n", n, + tev_a_input_table[ac.b]); + out.Write("\tcustom_data.tev_stages[{}].input_alpha[1].input_type = {};\n", n, + tev_a_input_type[ac.b]); + out.Write("\tcustom_data.tev_stages[{}].input_alpha[2].value = {} / float(255.0);\n", n, + tev_a_input_table[ac.c]); + out.Write("\tcustom_data.tev_stages[{}].input_alpha[2].input_type = {};\n", n, + tev_a_input_type[ac.c]); + out.Write("\tcustom_data.tev_stages[{}].input_alpha[3].value = {} / float(255.0);\n", n, + tev_a_input_table[ac.d]); + out.Write("\tcustom_data.tev_stages[{}].input_alpha[3].input_type = {};\n", n, + tev_a_input_type[ac.d]); + + // Texmap + out.Write("\tcustom_data.tev_stages[{}].texmap = {}u;\n", n, stage.tevorders_texmap); + + // Output + out.Write("\tcustom_data.tev_stages[{}].output_color.rgb = {} / float3(255.0, 255.0, 255.0);\n", + n, tev_c_output_table[cc.dest]); + out.Write("\tcustom_data.tev_stages[{}].output_color.a = {} / float(255.0);\n", n, + tev_a_output_table[ac.dest]); + } } static void WriteTevRegular(ShaderCode& out, std::string_view components, TevBias bias, TevOp op, diff --git a/Source/Core/VideoCommon/PixelShaderGen.h b/Source/Core/VideoCommon/PixelShaderGen.h index 84f1e27f74..456c72c2cd 100644 --- a/Source/Core/VideoCommon/PixelShaderGen.h +++ b/Source/Core/VideoCommon/PixelShaderGen.h @@ -158,8 +158,12 @@ struct pixel_shader_uid_data using PixelShaderUid = ShaderUid; +void WriteCustomShaderStructImpl(ShaderCode* out, u32 num_stages, bool per_pixel_lighting, + const pixel_shader_uid_data* uid_data); + ShaderCode GeneratePixelShaderCode(APIType api_type, const ShaderHostConfig& host_config, - const pixel_shader_uid_data* uid_data); + const pixel_shader_uid_data* uid_data, + const CustomPixelShaderContents& custom_details); void WritePixelShaderCommonHeader(ShaderCode& out, APIType api_type, const ShaderHostConfig& host_config, bool bounding_box); void ClearUnusedPixelShaderUidBits(APIType api_type, const ShaderHostConfig& host_config, diff --git a/Source/Core/VideoCommon/ShaderCache.cpp b/Source/Core/VideoCommon/ShaderCache.cpp index d1deef8424..63acab5517 100644 --- a/Source/Core/VideoCommon/ShaderCache.cpp +++ b/Source/Core/VideoCommon/ShaderCache.cpp @@ -449,7 +449,7 @@ ShaderCache::CompileVertexUberShader(const UberShader::VertexShaderUid& uid) con std::unique_ptr ShaderCache::CompilePixelShader(const PixelShaderUid& uid) const { const ShaderCode source_code = - GeneratePixelShaderCode(m_api_type, m_host_config, uid.GetUidData()); + GeneratePixelShaderCode(m_api_type, m_host_config, uid.GetUidData(), {}); return g_gfx->CreateShaderFromSource(ShaderStage::Pixel, source_code.GetBuffer()); } From 4283d7671804eb55862377cfc5f3966bd4b3394e Mon Sep 17 00:00:00 2001 From: iwubcode Date: Sat, 3 Dec 2022 23:54:35 -0600 Subject: [PATCH 04/12] VideoCommon: uber pixel shader gen changes needed to support custom pixel shaders in graphics mods --- Source/Core/VideoCommon/ShaderCache.cpp | 2 +- Source/Core/VideoCommon/UberShaderPixel.cpp | 572 ++++++++++++++++++-- Source/Core/VideoCommon/UberShaderPixel.h | 3 +- 3 files changed, 516 insertions(+), 61 deletions(-) diff --git a/Source/Core/VideoCommon/ShaderCache.cpp b/Source/Core/VideoCommon/ShaderCache.cpp index 63acab5517..7d11645949 100644 --- a/Source/Core/VideoCommon/ShaderCache.cpp +++ b/Source/Core/VideoCommon/ShaderCache.cpp @@ -457,7 +457,7 @@ std::unique_ptr ShaderCache::CompilePixelUberShader(const UberShader::PixelShaderUid& uid) const { const ShaderCode source_code = - UberShader::GenPixelShader(m_api_type, m_host_config, uid.GetUidData()); + UberShader::GenPixelShader(m_api_type, m_host_config, uid.GetUidData(), {}); return g_gfx->CreateShaderFromSource(ShaderStage::Pixel, source_code.GetBuffer(), fmt::to_string(*uid.GetUidData())); } diff --git a/Source/Core/VideoCommon/UberShaderPixel.cpp b/Source/Core/VideoCommon/UberShaderPixel.cpp index f54e42bf60..278be9c082 100644 --- a/Source/Core/VideoCommon/UberShaderPixel.cpp +++ b/Source/Core/VideoCommon/UberShaderPixel.cpp @@ -17,6 +17,257 @@ namespace UberShader { +namespace +{ +void WriteCustomShaderStructImpl(ShaderCode* out, u32 num_texgen, bool per_pixel_lighting) +{ + out->Write("\tCustomShaderData custom_data;\n"); + if (per_pixel_lighting) + { + out->Write("\tcustom_data.position = WorldPos;\n"); + out->Write("\tcustom_data.normal = Normal;\n"); + } + else + { + out->Write("\tcustom_data.position = float3(0, 0, 0);\n"); + out->Write("\tcustom_data.normal = float3(0, 0, 0);\n"); + } + + if (num_texgen == 0) [[unlikely]] + { + out->Write("\tcustom_data.texcoord[0] = float3(0, 0, 0);\n"); + } + else + { + for (u32 i = 0; i < num_texgen; ++i) + { + out->Write("\tif (tex{0}.z == 0.0)\n", i); + out->Write("\t{{\n"); + out->Write("\t\tcustom_data.texcoord[{0}] = tex{0};\n", i); + out->Write("\t}}\n"); + out->Write("\telse {{\n"); + out->Write("\t\tcustom_data.texcoord[{0}] = float3(tex{0}.xy / tex{0}.z, 0);\n", i); + out->Write("\t}}\n"); + } + } + + out->Write("\tcustom_data.texcoord_count = {};\n", num_texgen); + + for (u32 i = 0; i < 8; i++) + { + // Shader compilation complains if every index isn't initialized + out->Write("\tcustom_data.texmap_to_texcoord_index[{0}] = {0};\n", i); + } + + for (u32 i = 0; i < NUM_XF_COLOR_CHANNELS; i++) + { + out->Write("\tcustom_data.base_material[{}] = vec4(0, 0, 0, 1);\n", i); + out->Write("\tcustom_data.ambient_lighting[{}] = vec4(0, 0, 0, 1);\n", i); + + // Shader compilation errors can throw if not everything is initialized + for (u32 light_count_index = 0; light_count_index < 8; light_count_index++) + { + // Color + out->Write("\tcustom_data.lights_chan{}_color[{}].direction = float3(0, 0, 0);\n", i, + light_count_index); + out->Write("\tcustom_data.lights_chan{}_color[{}].position = float3(0, 0, 0);\n", i, + light_count_index); + out->Write("\tcustom_data.lights_chan{}_color[{}].color = float3(0, 0, 0);\n", i, + light_count_index); + out->Write("\tcustom_data.lights_chan{}_color[{}].cosatt = float4(0, 0, 0, 0);\n", i, + light_count_index); + out->Write("\tcustom_data.lights_chan{}_color[{}].distatt = float4(0, 0, 0, 0);\n", i, + light_count_index); + out->Write("\tcustom_data.lights_chan{}_color[{}].attenuation_type = 0;\n", i, + light_count_index); + + // Alpha + out->Write("\tcustom_data.lights_chan{}_alpha[{}].direction = float3(0, 0, 0);\n", i, + light_count_index); + out->Write("\tcustom_data.lights_chan{}_alpha[{}].position = float3(0, 0, 0);\n", i, + light_count_index); + out->Write("\tcustom_data.lights_chan{}_alpha[{}].color = float3(0, 0, 0);\n", i, + light_count_index); + out->Write("\tcustom_data.lights_chan{}_alpha[{}].cosatt = float4(0, 0, 0, 0);\n", i, + light_count_index); + out->Write("\tcustom_data.lights_chan{}_alpha[{}].distatt = float4(0, 0, 0, 0);\n", i, + light_count_index); + out->Write("\tcustom_data.lights_chan{}_alpha[{}].attenuation_type = 0;\n", i, + light_count_index); + } + + out->Write("\tcustom_data.light_chan{}_color_count = 0;\n", i); + out->Write("\tcustom_data.light_chan{}_alpha_count = 0;\n", i); + } + + if (num_texgen > 0) [[likely]] + { + out->Write("\n"); + out->Write("\tfor(uint stage = 0u; stage <= num_stages; stage++)\n"); + out->Write("\t{{\n"); + out->Write("\t\tStageState ss;\n"); + out->Write("\t\tss.order = bpmem_tevorder(stage>>1);\n"); + out->Write("\t\tif ((stage & 1u) == 1u)\n"); + out->Write("\t\t\tss.order = ss.order >> {};\n\n", + int(TwoTevStageOrders().enable_tex_odd.StartBit() - + TwoTevStageOrders().enable_tex_even.StartBit())); + out->Write("\t\tuint texmap = {};\n", + BitfieldExtract<&TwoTevStageOrders::texcoord_even>("ss.order")); + // Shader compilation is weird, shader arrays can't use indexing by variable + // to set values unless the variable is an index in a for loop. + // So instead we have to do this if check nonsense + for (u32 i = 0; i < 8; i++) + { + out->Write("\t\tif (texmap == {})\n", i); + out->Write("\t\t{{\n"); + out->Write("\t\t\tcustom_data.texmap_to_texcoord_index[{}] = selectTexCoordIndex(texmap);\n", + i); + out->Write("\t\t}}\n"); + } + out->Write("\t}}\n"); + } + + out->Write("\tuint light_count = 0;\n"); + out->Write("\tfor (uint chan = 0u; chan < {}u; chan++)\n", NUM_XF_COLOR_CHANNELS); + out->Write("\t{{\n"); + out->Write("\t\tuint colorreg = xfmem_color(chan);\n"); + out->Write("\t\tuint alphareg = xfmem_alpha(chan);\n"); + for (const auto& color_type : std::array{"colorreg", "alphareg"}) + { + if (color_type == "colorreg") + { + out->Write("\t\tcustom_data.base_material[0] = " I_MATERIALS "[2u] / 255.0; \n"); + out->Write("\t\tif ({} != 0u)\n", BitfieldExtract<&LitChannel::enablelighting>(color_type)); + out->Write("\t\t\tcustom_data.base_material[0] = colors_0; \n"); + } + else + { + out->Write("custom_data.base_material[1].w = " I_MATERIALS "[3u].w / 255.0; \n"); + out->Write("\t\tif ({} != 0u)\n", BitfieldExtract<&LitChannel::enablelighting>(color_type)); + out->Write("\t\t\tcustom_data.base_material[1].w = colors_1.w; \n"); + } + out->Write("\t\tif ({} != 0u)\n", BitfieldExtract<&LitChannel::enablelighting>(color_type)); + out->Write("\t\t{{\n"); + out->Write("\t\t\tuint light_mask = {} | ({} << 4u);\n", + BitfieldExtract<&LitChannel::lightMask0_3>(color_type), + BitfieldExtract<&LitChannel::lightMask4_7>(color_type)); + out->Write("\t\t\tuint attnfunc = {};\n", BitfieldExtract<&LitChannel::attnfunc>(color_type)); + out->Write("\t\t\tfor (uint light_index = 0u; light_index < 8u; light_index++)\n"); + out->Write("\t\t\t{{\n"); + out->Write("\t\t\t\tif ((light_mask & (1u << light_index)) != 0u)\n"); + out->Write("\t\t\t\t{{\n"); + // Shader compilation is weird, shader arrays can't use indexing by variable + // to set values unless the variable is an index in a for loop. + // So instead we have to do this if check nonsense + for (u32 light_count_index = 0; light_count_index < 8; light_count_index++) + { + out->Write("\t\t\t\t\tif (light_index == {})\n", light_count_index); + out->Write("\t\t\t\t\t{{\n"); + if (color_type == "colorreg") + { + for (u32 channel_index = 0; channel_index < NUM_XF_COLOR_CHANNELS; channel_index++) + { + out->Write("\t\t\t\t\t\tif (chan == {})\n", channel_index); + out->Write("\t\t\t\t\t\t{{\n"); + out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_color[{}].direction = " I_LIGHTS + "[light_index].dir.xyz;\n", + channel_index, light_count_index); + out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_color[{}].position = " I_LIGHTS + "[light_index].pos.xyz;\n", + channel_index, light_count_index); + out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_color[{}].cosatt = " I_LIGHTS + "[light_index].cosatt;\n", + channel_index, light_count_index); + out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_color[{}].distatt = " I_LIGHTS + "[light_index].distatt;\n", + channel_index, light_count_index); + out->Write( + "\t\t\t\t\t\t\tcustom_data.lights_chan{}_color[{}].attenuation_type = attnfunc;\n", + channel_index, light_count_index); + out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_color[{}].color = " I_LIGHTS + "[light_index].color.rgb / float3(255.0, 255.0, 255.0);\n", + channel_index, light_count_index); + out->Write("\t\t\t\t\t\t\tcustom_data.light_chan{}_color_count += 1;\n", channel_index); + out->Write("\t\t\t\t\t\t}}\n"); + } + } + else + { + for (u32 channel_index = 0; channel_index < NUM_XF_COLOR_CHANNELS; channel_index++) + { + out->Write("\t\t\t\t\t\tif (chan == {})\n", channel_index); + out->Write("\t\t\t\t\t\t{{\n"); + out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_alpha[{}].direction = " I_LIGHTS + "[light_index].dir.xyz;\n", + channel_index, light_count_index); + out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_alpha[{}].position = " I_LIGHTS + "[light_index].pos.xyz;\n", + channel_index, light_count_index); + out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_alpha[{}].cosatt = " I_LIGHTS + "[light_index].cosatt;\n", + channel_index, light_count_index); + out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_alpha[{}].distatt = " I_LIGHTS + "[light_index].distatt;\n", + channel_index, light_count_index); + out->Write( + "\t\t\t\t\t\t\tcustom_data.lights_chan{}_alpha[{}].attenuation_type = attnfunc;\n", + channel_index, light_count_index); + out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_alpha[{}].color = float3(" I_LIGHTS + "[light_index].color.a) / float3(255.0, 255.0, 255.0);\n", + channel_index, light_count_index); + out->Write("\t\t\t\t\t\t\tcustom_data.light_chan{}_alpha_count += 1;\n", channel_index); + out->Write("\t\t\t\t\t\t}}\n"); + } + } + + out->Write("\t\t\t\t\t}}\n"); + } + out->Write("\t\t\t\t}}\n"); + out->Write("\t\t\t}}\n"); + out->Write("\t\t}}\n"); + } + out->Write("\t}}\n"); + + for (u32 i = 0; i < 16; i++) + { + // Shader compilation complains if every struct isn't initialized + + // Color Input + for (u32 j = 0; j < 4; j++) + { + out->Write("\tcustom_data.tev_stages[{}].input_color[{}].input_type = " + "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_UNUSED;\n", + i, j); + out->Write("\tcustom_data.tev_stages[{}].input_color[{}].value = " + "float3(0, 0, 0);\n", + i, j); + } + + // Alpha Input + for (u32 j = 0; j < 4; j++) + { + out->Write("\tcustom_data.tev_stages[{}].input_alpha[{}].input_type = " + "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_UNUSED;\n", + i, j); + out->Write("\tcustom_data.tev_stages[{}].input_alpha[{}].value = " + "float(0);\n", + i, j); + } + + // Texmap + out->Write("\tcustom_data.tev_stages[{}].texmap = 0u;\n", i); + + // Output + out->Write("\tcustom_data.tev_stages[{}].output_color = " + "float4(0, 0, 0, 0);\n", + i); + } + + // Actual data will be filled out in the tev stage code, just set the + // stage count for now + out->Write("\tcustom_data.tev_stage_count = num_stages;\n"); +} +} // namespace PixelShaderUid GetPixelShaderUid() { PixelShaderUid out; @@ -56,7 +307,8 @@ void ClearUnusedPixelShaderUidBits(APIType api_type, const ShaderHostConfig& hos } ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config, - const pixel_ubershader_uid_data* uid_data) + const pixel_ubershader_uid_data* uid_data, + const CustomPixelShaderContents& custom_details) { const bool per_pixel_lighting = host_config.per_pixel_lighting; const bool msaa = host_config.msaa; @@ -76,6 +328,12 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config, out.Write("// {}\n", *uid_data); WriteBitfieldExtractHeader(out, api_type, host_config); WritePixelShaderCommonHeader(out, api_type, host_config, bounding_box); + WriteCustomShaderStructDef(&out, numTexgen); + for (std::size_t i = 0; i < custom_details.shaders.size(); i++) + { + const auto& shader_details = custom_details.shaders[i]; + out.Write(fmt::runtime(shader_details.custom_shader), i); + } if (per_pixel_lighting) WriteLightingFunction(out); @@ -228,6 +486,68 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config, } out.Write("}}\n\n"); + + out.Write("uint selectTexCoordIndex(uint texmap)"); + out.Write("{{\n"); + + if (api_type == APIType::D3D) + { + out.Write(" switch (texmap) {{\n"); + for (u32 i = 0; i < numTexgen; i++) + { + out.Write(" case {}u:\n" + " return {};\n", + i, i); + } + out.Write(" default:\n" + " return 0;\n" + " }}\n"); + } + else + { + out.Write(" if (texmap >= {}u) {{\n", numTexgen); + out.Write(" return 0;\n" + " }}\n"); + if (numTexgen > 4) + out.Write(" if (texmap < 4u) {{\n"); + if (numTexgen > 2) + out.Write(" if (texmap < 2u) {{\n"); + if (numTexgen > 1) + out.Write(" return (texmap == 0u) ? 0 : 1;\n"); + else + out.Write(" return 0;\n"); + if (numTexgen > 2) + { + out.Write(" }} else {{\n"); // >= 2 < min(4, numTexgen) + if (numTexgen > 3) + out.Write(" return (texmap == 2u) ? 2 : 3;\n"); + else + out.Write(" return 2;\n"); + out.Write(" }}\n"); + } + if (numTexgen > 4) + { + out.Write(" }} else {{\n"); // >= 4 < min(8, numTexgen) + if (numTexgen > 6) + out.Write(" if (texmap < 6u) {{\n"); + if (numTexgen > 5) + out.Write(" return (texmap == 4u) ? 4 : 5;\n"); + else + out.Write(" return 4;\n"); + if (numTexgen > 6) + { + out.Write(" }} else {{\n"); // >= 6 < min(8, numTexgen) + if (numTexgen > 7) + out.Write(" return (texmap == 6u) ? 6 : 7;\n"); + else + out.Write(" return 6;\n"); + out.Write(" }}\n"); + } + out.Write(" }}\n"); + } + } + + out.Write("}}\n\n"); } // ===================== @@ -316,43 +636,43 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config, // TEV's Special Lerp // ====================== const auto WriteTevLerp = [&out](std::string_view components) { - out.Write( - "// TEV's Linear Interpolate, plus bias, add/subtract and scale\n" - "int{0} tevLerp{0}(int{0} A, int{0} B, int{0} C, int{0} D, uint bias, bool op, " - "uint scale) {{\n" - " // Scale C from 0..255 to 0..256\n" - " C += C >> 7;\n" - "\n" - " // Add bias to D\n" - " if (bias == 1u) D += 128;\n" - " else if (bias == 2u) D -= 128;\n" - "\n" - " int{0} lerp = (A << 8) + (B - A)*C;\n" - " if (scale != 3u) {{\n" - " lerp = lerp << scale;\n" - " D = D << scale;\n" - " }}\n" - "\n" - " // TODO: Is this rounding bias still added when the scale is divide by 2? Currently we " - "do not apply it.\n" - " if (scale != 3u)\n" - " lerp = lerp + (op ? 127 : 128);\n" - "\n" - " int{0} result = lerp >> 8;\n" - "\n" - " // Add/Subtract D\n" - " if (op) // Subtract\n" - " result = D - result;\n" - " else // Add\n" - " result = D + result;\n" - "\n" - " // Most of the Scale was moved inside the lerp for improved precision\n" - " // But we still do the divide by 2 here\n" - " if (scale == 3u)\n" - " result = result >> 1;\n" - " return result;\n" - "}}\n\n", - components); + out.Write("// TEV's Linear Interpolate, plus bias, add/subtract and scale\n" + "int{0} tevLerp{0}(int{0} A, int{0} B, int{0} C, int{0} D, uint bias, bool op, " + "uint scale) {{\n" + " // Scale C from 0..255 to 0..256\n" + " C += C >> 7;\n" + "\n" + " // Add bias to D\n" + " if (bias == 1u) D += 128;\n" + " else if (bias == 2u) D -= 128;\n" + "\n" + " int{0} lerp = (A << 8) + (B - A)*C;\n" + " if (scale != 3u) {{\n" + " lerp = lerp << scale;\n" + " D = D << scale;\n" + " }}\n" + "\n" + " // TODO: Is this rounding bias still added when the scale is divide by 2? " + "Currently we " + "do not apply it.\n" + " if (scale != 3u)\n" + " lerp = lerp + (op ? 127 : 128);\n" + "\n" + " int{0} result = lerp >> 8;\n" + "\n" + " // Add/Subtract D\n" + " if (op) // Subtract\n" + " result = D - result;\n" + " else // Add\n" + " result = D + result;\n" + "\n" + " // Most of the Scale was moved inside the lerp for improved precision\n" + " // But we still do the divide by 2 here\n" + " if (scale == 3u)\n" + " result = result >> 1;\n" + " return result;\n" + "}}\n\n", + components); }; WriteTevLerp(""); // int WriteTevLerp("3"); // int3 @@ -437,6 +757,25 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config, "return int3(0, 0, 0);", // ZERO }; + static constexpr Common::EnumMap tev_c_input_type{ + "return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_PREV;", + "return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_PREV;", + "return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR;", + "return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR;", + "return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR;", + "return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR;", + "return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR;", + "return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR;", + "return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_TEX;", + "return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_TEX;", + "return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_RAS;", + "return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_RAS;", + "return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_NUMERIC;", + "return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_NUMERIC;", + "return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_KONST;", + "return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_NUMERIC;", + }; + static constexpr Common::EnumMap tev_a_input_table{ "return s.Reg[0].a;", // APREV, "return s.Reg[1].a;", // A0, @@ -448,6 +787,17 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config, "return 0;", // ZERO }; + static constexpr Common::EnumMap tev_a_input_type{ + "return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_PREV;", + "return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR;", + "return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR;", + "return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR;", + "return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_TEX;", + "return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_RAS;", + "return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_KONST;", + "return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_NUMERIC;", + }; + static constexpr Common::EnumMap tev_regs_lookup_table{ "return s.Reg[0];", "return s.Reg[1];", @@ -489,6 +839,16 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config, out.Write("}}\n" "\n"); + out.Write("// Helper function for Custom Shader Input Type\n" + "uint getColorInputType(uint index) {{\n"); + WriteSwitch(out, api_type, "index", tev_c_input_type, 2, false); + out.Write("}}\n" + "\n" + "uint getAlphaInputType(uint index) {{\n"); + WriteSwitch(out, api_type, "index", tev_a_input_type, 2, false); + out.Write("}}\n" + "\n"); + // Since the fixed-point texture coodinate variables aren't global, we need to pass // them to the select function. This applies to all backends. if (numTexgen > 0) @@ -505,6 +865,17 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config, out.Write("void main()\n{{\n"); out.Write(" float4 rawpos = gl_FragCoord;\n"); + out.Write(" uint num_stages = {};\n\n", + BitfieldExtract<&GenMode::numtevstages>("bpmem_genmode")); + + bool has_custom_shader_details = false; + if (std::any_of(custom_details.shaders.begin(), custom_details.shaders.end(), + [](const std::optional& ps) { return ps.has_value(); })) + { + WriteCustomShaderStructImpl(&out, numTexgen, per_pixel_lighting); + has_custom_shader_details = true; + } + if (use_framebuffer_fetch) { // Store off a copy of the initial framebuffer value. @@ -563,9 +934,6 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config, " // o.colors_1 = float4(0.0, 0.0, 0.0, 0.0);\n"); } - out.Write(" uint num_stages = {};\n\n", - BitfieldExtract<&GenMode::numtevstages>("bpmem_genmode")); - out.Write(" // Main tev loop\n"); out.Write(" for(uint stage = 0u; stage <= num_stages; stage++)\n" @@ -618,9 +986,9 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config, // indirect texture stage is enabled). If the matrix is off, the result doesn't matter; if the // indirect texture stage is disabled, the result is undefined (and produces a glitchy pattern // on hardware, different from this). - // For the undefined case, we just skip applying the indirect operation, which is close enough. - // Viewtiful Joe hits the undefined case (bug 12525). - // Wrapping and add to previous still apply in this case (and when the stage is disabled). + // For the undefined case, we just skip applying the indirect operation, which is close + // enough. Viewtiful Joe hits the undefined case (bug 12525). Wrapping and add to previous + // still apply in this case (and when the stage is disabled). out.Write(" if (bpmem_iref(bt) != 0u) {{\n"); out.Write(" int3 indcoord;\n"); LookupIndirectTexture("indcoord", "bt"); @@ -826,7 +1194,8 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config, " alpha_B = selectAlphaInput(s, ss, {0}colors_0, {0}colors_1, alpha_b) & 255;\n" " }};\n" " int alpha_C = selectAlphaInput(s, ss, {0}colors_0, {0}colors_1, alpha_c) & 255;\n" - " int alpha_D = selectAlphaInput(s, ss, {0}colors_0, {0}colors_1, alpha_d); // 10 bits " + " int alpha_D = selectAlphaInput(s, ss, {0}colors_0, {0}colors_1, alpha_d); // 10 " + "bits " "+ sign\n" "\n", // TODO: do we need to sign extend? color_input_prefix); @@ -857,9 +1226,81 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config, "\n" " // Write result to the correct input register of the next stage\n"); WriteSwitch(out, api_type, "alpha_dest", tev_a_set_table, 6, true); - out.Write(" }}\n" - " }} // Main TEV loop\n" - "\n"); + if (has_custom_shader_details) + { + for (u32 stage_index = 0; stage_index < 16; stage_index++) + { + out.Write("\tif (stage == {}u) {{\n", stage_index); + // Color input + out.Write("\t\tcustom_data.tev_stages[{}].input_color[0].value = color_A / float3(255.0, " + "255.0, 255.0);\n", + stage_index); + out.Write("\t\tcustom_data.tev_stages[{}].input_color[0].input_type = " + "getColorInputType(color_a);\n", + stage_index); + out.Write("\t\tcustom_data.tev_stages[{}].input_color[1].value = color_B / float3(255.0, " + "255.0, 255.0);\n", + stage_index); + out.Write("\t\tcustom_data.tev_stages[{}].input_color[1].input_type = " + "getColorInputType(color_b);\n", + stage_index); + out.Write("\t\tcustom_data.tev_stages[{}].input_color[2].value = color_C / float3(255.0, " + "255.0, 255.0);\n", + stage_index); + out.Write("\t\tcustom_data.tev_stages[{}].input_color[2].input_type = " + "getColorInputType(color_c);\n", + stage_index); + out.Write("\t\tcustom_data.tev_stages[{}].input_color[3].value = color_D / float3(255.0, " + "255.0, 255.0);\n", + stage_index); + out.Write("\t\tcustom_data.tev_stages[{}].input_color[3].input_type = " + "getColorInputType(color_c);\n", + stage_index); + + // Alpha input + out.Write("\t\tcustom_data.tev_stages[{}].input_alpha[0].value = alpha_A / float(255.0);\n", + stage_index); + out.Write("\t\tcustom_data.tev_stages[{}].input_alpha[0].input_type = " + "getAlphaInputType(alpha_a);\n", + stage_index); + out.Write("\t\tcustom_data.tev_stages[{}].input_alpha[1].value = alpha_B / float(255.0);\n", + stage_index); + out.Write("\t\tcustom_data.tev_stages[{}].input_alpha[1].input_type = " + "getAlphaInputType(alpha_b);\n", + stage_index); + out.Write("\t\tcustom_data.tev_stages[{}].input_alpha[2].value = alpha_C / float(255.0);\n", + stage_index); + out.Write("\t\tcustom_data.tev_stages[{}].input_alpha[2].input_type = " + "getAlphaInputType(alpha_c);\n", + stage_index); + out.Write("\t\tcustom_data.tev_stages[{}].input_alpha[3].value = alpha_D / float(255.0);\n", + stage_index); + out.Write("\t\tcustom_data.tev_stages[{}].input_alpha[3].input_type = " + "getAlphaInputType(alpha_d);\n", + stage_index); + + if (numTexgen != 0) + { + // Texmap + out.Write("\t\tif (texture_enabled) {{\n"); + out.Write("\t\t\tuint sampler_num = {};\n", + BitfieldExtract<&TwoTevStageOrders::texmap_even>("ss.order")); + out.Write("\t\tcustom_data.tev_stages[{}].texmap = sampler_num;\n", stage_index); + out.Write("\t\t}}\n"); + } + + // Output + out.Write("\t\tcustom_data.tev_stages[{}].output_color.rgb = color / float3(255.0, 255.0, " + "255.0);\n", + stage_index); + out.Write("\t\tcustom_data.tev_stages[{}].output_color.a = alpha / float(255.0);\n", + stage_index); + out.Write("\t}}\n"); + } + } + out.Write(" }}\n"); + out.Write(" }} // Main TEV loop\n"); + out.Write("\n"); // Select the output color and alpha registers from the last stage. out.Write(" int4 TevResult;\n"); @@ -942,8 +1383,8 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config, { // Instead of using discard, fetch the framebuffer's color value and use it as the output // for this fragment. - out.Write( - " #define discard_fragment {{ real_ocol0 = float4(initial_ocol0.xyz, 1.0); return; }}\n"); + out.Write(" #define discard_fragment {{ real_ocol0 = float4(initial_ocol0.xyz, 1.0); " + "return; }}\n"); } else { @@ -1109,8 +1550,8 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config, " }}\n"); } - // Some backends require that the shader outputs be uint when writing to a uint render target for - // logic op. + // Some backends require that the shader outputs be uint when writing to a uint render target + // for logic op. if (uid_data->uint_output) { out.Write(" if (bpmem_rgba6_format)\n" @@ -1142,6 +1583,19 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config, } } + for (std::size_t i = 0; i < custom_details.shaders.size(); i++) + { + const auto& shader_details = custom_details.shaders[i]; + + if (!shader_details.custom_shader.empty()) + { + out.Write("\t{{\n"); + out.Write("\t\tcustom_data.final_color = ocol0;\n"); + out.Write("\t\tocol0.xyz = {}_{}(custom_data).xyz;\n", CUSTOM_PIXELSHADER_COLOR_FUNC, i); + out.Write("\t}}\n\n"); + } + } + if (bounding_box) { out.Write(" if (bpmem_bounding_box) {{\n" @@ -1209,13 +1663,13 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config, WriteSwitch(out, api_type, "blend_dst_factor", blendDstFactor, 4, true); WriteSwitch(out, api_type, "blend_dst_factor_alpha", blendDstFactorAlpha, 4, true); - out.Write( - " float4 blend_result;\n" - " if (blend_subtract)\n" - " blend_result.rgb = initial_ocol0.rgb * blend_dst.rgb - ocol0.rgb * blend_src.rgb;\n" - " else\n" - " blend_result.rgb = initial_ocol0.rgb * blend_dst.rgb + ocol0.rgb * " - "blend_src.rgb;\n"); + out.Write(" float4 blend_result;\n" + " if (blend_subtract)\n" + " blend_result.rgb = initial_ocol0.rgb * blend_dst.rgb - ocol0.rgb * " + "blend_src.rgb;\n" + " else\n" + " blend_result.rgb = initial_ocol0.rgb * blend_dst.rgb + ocol0.rgb * " + "blend_src.rgb;\n"); out.Write(" if (blend_subtract_alpha)\n" " blend_result.a = initial_ocol0.a * blend_dst.a - ocol0.a * blend_src.a;\n" diff --git a/Source/Core/VideoCommon/UberShaderPixel.h b/Source/Core/VideoCommon/UberShaderPixel.h index aa3d6c7625..80560688ca 100644 --- a/Source/Core/VideoCommon/UberShaderPixel.h +++ b/Source/Core/VideoCommon/UberShaderPixel.h @@ -29,7 +29,8 @@ using PixelShaderUid = ShaderUid; PixelShaderUid GetPixelShaderUid(); ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config, - const pixel_ubershader_uid_data* uid_data); + const pixel_ubershader_uid_data* uid_data, + const CustomPixelShaderContents& custom_details); void EnumeratePixelShaderUids(const std::function& callback); void ClearUnusedPixelShaderUidBits(APIType api_type, const ShaderHostConfig& host_config, From dbaf24ef092ae6e3ea5d12789878a0c17c97c72b Mon Sep 17 00:00:00 2001 From: iwubcode Date: Sat, 15 Oct 2022 14:54:21 -0500 Subject: [PATCH 05/12] VideoCommon: add data needed to support custom pixel shaders to graphics mod actions --- .../GraphicsModSystem/Runtime/GraphicsModActionData.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionData.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionData.h index 2b1408dcc9..a9efb6a0dd 100644 --- a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionData.h +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionData.h @@ -3,18 +3,23 @@ #pragma once +#include +#include #include #include #include "Common/CommonTypes.h" #include "Common/Matrix.h" #include "VideoCommon/Assets/TextureAsset.h" +#include "VideoCommon/PixelShaderGen.h" namespace GraphicsModActionData { struct DrawStarted { + u32 texture_unit; bool* skip; + std::optional* custom_pixel_shader; }; struct EFB From bedbf2b8c6fbf5485ba773b3ff96aa632c2780d2 Mon Sep 17 00:00:00 2001 From: iwubcode Date: Wed, 17 Aug 2022 01:26:06 -0500 Subject: [PATCH 06/12] VideoCommon: add custom shader cache --- Source/Core/DolphinLib.props | 2 + Source/Core/VideoCommon/CMakeLists.txt | 2 + .../Runtime/CustomShaderCache.cpp | 376 ++++++++++++++++++ .../Runtime/CustomShaderCache.h | 144 +++++++ 4 files changed, 524 insertions(+) create mode 100644 Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomShaderCache.cpp create mode 100644 Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomShaderCache.h diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index 1b44f15ec4..3e2a4f2176 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -672,6 +672,7 @@ + @@ -1284,6 +1285,7 @@ + diff --git a/Source/Core/VideoCommon/CMakeLists.txt b/Source/Core/VideoCommon/CMakeLists.txt index ba3b67faba..bd816052ba 100644 --- a/Source/Core/VideoCommon/CMakeLists.txt +++ b/Source/Core/VideoCommon/CMakeLists.txt @@ -81,6 +81,8 @@ add_library(videocommon GraphicsModSystem/Runtime/Actions/ScaleAction.h GraphicsModSystem/Runtime/Actions/SkipAction.cpp GraphicsModSystem/Runtime/Actions/SkipAction.h + GraphicsModSystem/Runtime/CustomShaderCache.cpp + GraphicsModSystem/Runtime/CustomShaderCache.h GraphicsModSystem/Runtime/FBInfo.cpp GraphicsModSystem/Runtime/FBInfo.h GraphicsModSystem/Runtime/GraphicsModAction.h diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomShaderCache.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomShaderCache.cpp new file mode 100644 index 0000000000..27112846c6 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomShaderCache.cpp @@ -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 +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 +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( + 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 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( + 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( + 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 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( + 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 pipeline) +{ + iterator->second.pending = false; + iterator->second.value = std::move(pipeline); +} + +void CustomShaderCache::NotifyPipelineFinished(UberPipelineIterator iterator, + std::unique_ptr 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 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( + 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 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( + this, uid, custom_shaders, list_iter); + m_async_uber_shader_compiler->QueueWorkItem(std::move(work_item), 0); +} + +std::unique_ptr +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 +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 shader) +{ + iterator->second.pending = false; + iterator->second.value = std::move(shader); +} + +void CustomShaderCache::NotifyPixelShaderFinished(UberPixelShaderIterator iterator, + std::unique_ptr shader) +{ + iterator->second.pending = false; + iterator->second.value = std::move(shader); +} diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomShaderCache.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomShaderCache.h new file mode 100644 index 0000000000..ff2aba2823 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomShaderCache.h @@ -0,0 +1,144 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#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 + GetPipelineAsync(const VideoCommon::GXPipelineUid& uid, + const CustomShaderInstance& custom_shaders, + const AbstractPipelineConfig& pipeline_config); + std::optional + 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 m_async_shader_compiler; + std::unique_ptr 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 + struct Cache + { + struct CacheHolder + { + std::unique_ptr value = nullptr; + bool pending = true; + }; + using CacheElement = std::pair; + using CacheList = std::list; + std::map 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 m_ps_cache; + Cache m_uber_ps_cache; + Cache m_pipeline_cache; + Cache m_uber_pipeline_cache; + + using PipelineIterator = Cache::CacheList::iterator; + using UberPipelineIterator = + Cache::CacheList::iterator; + using PixelShaderIterator = Cache::CacheList::iterator; + using UberPixelShaderIterator = + Cache::CacheList::iterator; + + void NotifyPipelineFinished(PipelineIterator iterator, + std::unique_ptr pipeline); + void NotifyPipelineFinished(UberPipelineIterator iterator, + std::unique_ptr pipeline); + + std::unique_ptr + CompilePixelShader(const PixelShaderUid& uid, const CustomShaderInstance& custom_shaders) const; + void NotifyPixelShaderFinished(PixelShaderIterator iterator, + std::unique_ptr shader); + std::unique_ptr + CompilePixelShader(const UberShader::PixelShaderUid& uid, + const CustomShaderInstance& custom_shaders) const; + void NotifyPixelShaderFinished(UberPixelShaderIterator iterator, + std::unique_ptr 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; +}; From d32036695483583b0aa17ad454972f8a01222682 Mon Sep 17 00:00:00 2001 From: iwubcode Date: Sun, 18 Sep 2022 01:00:42 -0500 Subject: [PATCH 07/12] VideoCommon: add custom shader cache to VertexManagerBase, supporting custom pixel shaders by replacing the existing pipeline with a modified one --- Source/Core/VideoCommon/VertexManagerBase.cpp | 92 ++++++++++++++++++- Source/Core/VideoCommon/VertexManagerBase.h | 4 + 2 files changed, 92 insertions(+), 4 deletions(-) diff --git a/Source/Core/VideoCommon/VertexManagerBase.cpp b/Source/Core/VideoCommon/VertexManagerBase.cpp index 0b184e1da9..92e5be9952 100644 --- a/Source/Core/VideoCommon/VertexManagerBase.cpp +++ b/Source/Core/VideoCommon/VertexManagerBase.cpp @@ -23,12 +23,14 @@ #include "VideoCommon/DataReader.h" #include "VideoCommon/FramebufferManager.h" #include "VideoCommon/GeometryShaderManager.h" +#include "VideoCommon/GraphicsModSystem/Runtime/CustomShaderCache.h" #include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionData.h" #include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h" #include "VideoCommon/IndexGenerator.h" #include "VideoCommon/NativeVertexFormat.h" #include "VideoCommon/OpcodeDecoding.h" #include "VideoCommon/PerfQueryBase.h" +#include "VideoCommon/PixelShaderGen.h" #include "VideoCommon/PixelShaderManager.h" #include "VideoCommon/Statistics.h" #include "VideoCommon/TextureCacheBase.h" @@ -106,6 +108,7 @@ bool VertexManagerBase::Initialize() { m_frame_end_event = AfterFrameEvent::Register([this] { OnEndFrame(); }, "VertexManagerBase"); m_index_generator.Init(); + m_custom_shader_cache = std::make_unique(); m_cpu_cull.Init(); return true; } @@ -527,6 +530,7 @@ void VertexManagerBase::Flush() // Calculate ZSlope for zfreeze const auto used_textures = UsedTextures(); std::vector texture_names; + std::vector texture_units; if (!m_cull_all) { if (!g_ActiveConfig.bGraphicMods) @@ -543,7 +547,12 @@ void VertexManagerBase::Flush() const auto cache_entry = g_texture_cache->Load(TextureInfo::FromStage(i)); if (cache_entry) { - texture_names.push_back(cache_entry->texture_info_name); + if (std::find(texture_names.begin(), texture_names.end(), + cache_entry->texture_info_name) == texture_names.end()) + { + texture_names.push_back(cache_entry->texture_info_name); + texture_units.push_back(i); + } } } } @@ -562,13 +571,24 @@ void VertexManagerBase::Flush() if (!m_cull_all) { - for (const auto& texture_name : texture_names) + CustomPixelShaderContents custom_pixel_shader_contents; + std::optional custom_pixel_shader; + std::vector custom_pixel_texture_names; + for (int i = 0; i < texture_names.size(); i++) { + const std::string& texture_name = texture_names[i]; + const u32 texture_unit = texture_units[i]; bool skip = false; - GraphicsModActionData::DrawStarted draw_started{&skip}; + GraphicsModActionData::DrawStarted draw_started{texture_unit, &skip, &custom_pixel_shader}; for (const auto& action : g_graphics_mod_manager->GetDrawStartedActions(texture_name)) { action->OnDrawStarted(&draw_started); + if (custom_pixel_shader) + { + custom_pixel_shader_contents.shaders.push_back(*custom_pixel_shader); + custom_pixel_texture_names.push_back(texture_name); + } + custom_pixel_shader = std::nullopt; } if (skip == true) return; @@ -610,7 +630,65 @@ void VertexManagerBase::Flush() UpdatePipelineObject(); if (m_current_pipeline_object) { - g_gfx->SetPipeline(m_current_pipeline_object); + const AbstractPipeline* current_pipeline = m_current_pipeline_object; + if (!custom_pixel_shader_contents.shaders.empty()) + { + CustomShaderInstance custom_shaders; + custom_shaders.pixel_contents = std::move(custom_pixel_shader_contents); + + switch (g_ActiveConfig.iShaderCompilationMode) + { + case ShaderCompilationMode::Synchronous: + case ShaderCompilationMode::AsynchronousSkipRendering: + { + if (auto pipeline = m_custom_shader_cache->GetPipelineAsync( + m_current_pipeline_config, custom_shaders, m_current_pipeline_object->m_config)) + { + current_pipeline = *pipeline; + } + } + break; + case ShaderCompilationMode::SynchronousUberShaders: + { + // D3D has issues compiling large custom ubershaders + // use specialized shaders instead + if (g_ActiveConfig.backend_info.api_type == APIType::D3D) + { + if (auto pipeline = m_custom_shader_cache->GetPipelineAsync( + m_current_pipeline_config, custom_shaders, m_current_pipeline_object->m_config)) + { + current_pipeline = *pipeline; + } + } + else + { + if (auto pipeline = m_custom_shader_cache->GetPipelineAsync( + m_current_uber_pipeline_config, custom_shaders, + m_current_pipeline_object->m_config)) + { + current_pipeline = *pipeline; + } + } + } + break; + case ShaderCompilationMode::AsynchronousUberShaders: + { + if (auto pipeline = m_custom_shader_cache->GetPipelineAsync( + m_current_pipeline_config, custom_shaders, m_current_pipeline_object->m_config)) + { + current_pipeline = *pipeline; + } + else if (auto uber_pipeline = m_custom_shader_cache->GetPipelineAsync( + m_current_uber_pipeline_config, custom_shaders, + m_current_pipeline_object->m_config)) + { + current_pipeline = *uber_pipeline; + } + } + break; + }; + } + g_gfx->SetPipeline(current_pipeline); if (PerfQueryBase::ShouldEmulate()) g_perf_query->EnableQuery(bpmem.zcontrol.early_ztest ? PQG_ZCOMP_ZCOMPLOC : PQG_ZCOMP); @@ -1006,3 +1084,9 @@ void VertexManagerBase::OnEndFrame() // state changes the specialized shader will not take over. InvalidatePipelineObject(); } + +void VertexManagerBase::NotifyCustomShaderCacheOfHostChange(const ShaderHostConfig& host_config) +{ + m_custom_shader_cache->SetHostConfig(host_config); + m_custom_shader_cache->Reload(); +} diff --git a/Source/Core/VideoCommon/VertexManagerBase.h b/Source/Core/VideoCommon/VertexManagerBase.h index 775d8e2787..66e24cbf36 100644 --- a/Source/Core/VideoCommon/VertexManagerBase.h +++ b/Source/Core/VideoCommon/VertexManagerBase.h @@ -15,6 +15,7 @@ #include "VideoCommon/ShaderCache.h" #include "VideoCommon/VideoEvents.h" +class CustomShaderCache; class DataReader; class NativeVertexFormat; class PointerWrap; @@ -128,6 +129,7 @@ public: m_current_pipeline_object = nullptr; m_pipeline_config_changed = true; } + void NotifyCustomShaderCacheOfHostChange(const ShaderHostConfig& host_config); // Utility pipeline drawing (e.g. EFB copies, post-processing, UI). virtual void UploadUtilityUniforms(const void* uniforms, u32 uniforms_size); @@ -230,6 +232,8 @@ private: std::vector m_scheduled_command_buffer_kicks; bool m_allow_background_execution = true; + std::unique_ptr m_custom_shader_cache; + Common::EventHook m_frame_end_event; }; From c7191382bed2ba2c64fa5a303c2bfa61778a5d97 Mon Sep 17 00:00:00 2001 From: iwubcode Date: Sun, 9 Jul 2023 15:23:08 -0500 Subject: [PATCH 08/12] VideoCommon: add custom pipeline action --- Source/Core/DolphinLib.props | 2 + Source/Core/VideoCommon/CMakeLists.txt | 2 + .../Runtime/Actions/CustomPipelineAction.cpp | 449 ++++++++++++++++++ .../Runtime/Actions/CustomPipelineAction.h | 53 +++ .../Runtime/GraphicsModActionFactory.cpp | 5 + 5 files changed, 511 insertions(+) create mode 100644 Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.cpp create mode 100644 Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.h diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index 3e2a4f2176..b8c7162114 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -668,6 +668,7 @@ + @@ -1281,6 +1282,7 @@ + diff --git a/Source/Core/VideoCommon/CMakeLists.txt b/Source/Core/VideoCommon/CMakeLists.txt index bd816052ba..99915a0023 100644 --- a/Source/Core/VideoCommon/CMakeLists.txt +++ b/Source/Core/VideoCommon/CMakeLists.txt @@ -73,6 +73,8 @@ add_library(videocommon GraphicsModSystem/Config/GraphicsTargetGroup.cpp GraphicsModSystem/Config/GraphicsTargetGroup.h GraphicsModSystem/Constants.h + GraphicsModSystem/Runtime/Actions/CustomPipelineAction.cpp + GraphicsModSystem/Runtime/Actions/CustomPipelineAction.h GraphicsModSystem/Runtime/Actions/MoveAction.cpp GraphicsModSystem/Runtime/Actions/MoveAction.h GraphicsModSystem/Runtime/Actions/PrintAction.cpp diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.cpp new file mode 100644 index 0000000000..b774324a58 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.cpp @@ -0,0 +1,449 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.h" + +#include +#include + +#include + +#include "Common/FileUtil.h" +#include "Common/Logging/Log.h" +#include "Common/StringUtil.h" +#include "Core/System.h" + +#include "VideoCommon/AbstractGfx.h" +#include "VideoCommon/Assets/CustomAssetLoader.h" +#include "VideoCommon/Assets/DirectFilesystemAssetLibrary.h" +#include "VideoCommon/ShaderGenCommon.h" +#include "VideoCommon/TextureCacheBase.h" + +namespace +{ +bool IsQualifier(std::string_view value) +{ + static std::array qualifiers = {"attribute", "const", "highp", "lowp", + "mediump", "uniform", "varying"}; + return std::find(qualifiers.begin(), qualifiers.end(), value) != qualifiers.end(); +} + +bool IsBuiltInMacro(std::string_view value) +{ + static std::array built_in = {"__LINE__", "__FILE__", "__VERSION__", + "GL_core_profile", "GL_compatibility_profile"}; + return std::find(built_in.begin(), built_in.end(), value) != built_in.end(); +} + +std::vector GlobalConflicts(std::string_view source) +{ + std::string_view last_identifier = ""; + std::vector global_result; + u32 scope = 0; + for (u32 i = 0; i < source.size(); i++) + { + // If we're out of global scope, we don't care + // about any of the details + if (scope > 0) + { + if (source[i] == '{') + { + scope++; + } + else if (source[i] == '}') + { + scope--; + } + continue; + } + + const auto parse_identifier = [&]() { + const u32 start = i; + for (; i < source.size(); i++) + { + if (!Common::IsAlpha(source[i]) && source[i] != '_' && !std::isdigit(source[i])) + break; + } + u32 end = i; + i--; // unwind + return source.substr(start, end - start); + }; + + if (Common::IsAlpha(source[i]) || source[i] == '_') + { + const std::string_view identifier = parse_identifier(); + if (IsQualifier(identifier)) + continue; + if (IsBuiltInMacro(identifier)) + continue; + last_identifier = identifier; + } + else if (source[i] == '#') + { + const auto parse_until_end_of_preprocessor = [&]() { + bool continue_until_next_newline = false; + for (; i < source.size(); i++) + { + if (source[i] == '\n') + { + if (continue_until_next_newline) + continue_until_next_newline = false; + else + break; + } + else if (source[i] == '\\') + { + continue_until_next_newline = true; + } + } + }; + i++; + const std::string_view identifier = parse_identifier(); + if (identifier == "define") + { + i++; + // skip whitespace + while (source[i] == ' ') + { + i++; + } + global_result.push_back(std::string{parse_identifier()}); + parse_until_end_of_preprocessor(); + } + else + { + parse_until_end_of_preprocessor(); + } + } + else if (source[i] == '{') + { + scope++; + } + else if (source[i] == '(') + { + // Unlikely the user will be using layouts but... + if (last_identifier == "layout") + continue; + + // Since we handle equality, we can assume the identifier + // before '(' is a function definition + global_result.push_back(std::string{last_identifier}); + } + else if (source[i] == '=') + { + global_result.push_back(std::string{last_identifier}); + i++; + for (; i < source.size(); i++) + { + if (source[i] == ';') + break; + } + } + else if (source[i] == '/') + { + if ((i + 1) >= source.size()) + continue; + + if (source[i + 1] == '/') + { + // Go to end of line... + for (; i < source.size(); i++) + { + if (source[i] == '\n') + break; + } + } + else if (source[i + 1] == '*') + { + // Multiline, look for first '*/' + for (; i < source.size(); i++) + { + if (source[i] == '/' && source[i - 1] == '*') + break; + } + } + } + } + + // Sort the conflicts from largest to smallest string + // this way we can ensure smaller strings that are a substring + // of the larger string are able to be replaced appropriately + std::sort(global_result.begin(), global_result.end(), + [](const std::string& first, const std::string& second) { + return first.size() > second.size(); + }); + return global_result; +} + +void WriteDefines(ShaderCode* out, const std::vector& texture_code_names, + u32 texture_unit) +{ + for (std::size_t i = 0; i < texture_code_names.size(); i++) + { + const auto& code_name = texture_code_names[i]; + out->Write("#define {}_UNIT_{{0}} {}\n", code_name, texture_unit); + out->Write( + "#define {0}_COORD_{{0}} float3(data.texcoord[data.texmap_to_texcoord_index[{1}]].xy, " + "{2})\n", + code_name, texture_unit, i + 1); + } +} + +} // namespace + +std::unique_ptr +CustomPipelineAction::Create(const picojson::value& json_data, + std::shared_ptr library) +{ + std::vector pipeline_passes; + + const auto& passes_json = json_data.get("passes"); + if (passes_json.is()) + { + for (const auto& passes_json_val : passes_json.get()) + { + CustomPipelineAction::PipelinePassPassDescription pipeline_pass; + if (!passes_json_val.is()) + { + ERROR_LOG_FMT(VIDEO, + "Failed to load custom pipeline action, 'passes' has an array value that " + "is not an object!"); + return nullptr; + } + + auto pass = passes_json_val.get(); + if (!pass.contains("pixel_material_asset")) + { + ERROR_LOG_FMT(VIDEO, + "Failed to load custom pipeline action, 'passes' value missing required " + "field 'pixel_material_asset'"); + return nullptr; + } + + auto pixel_material_asset_json = pass["pixel_material_asset"]; + if (!pixel_material_asset_json.is()) + { + ERROR_LOG_FMT(VIDEO, "Failed to load custom pipeline action, 'passes' field " + "'pixel_material_asset' is not a string!"); + return nullptr; + } + pipeline_pass.m_pixel_material_asset = pixel_material_asset_json.to_str(); + pipeline_passes.push_back(std::move(pipeline_pass)); + } + } + + if (pipeline_passes.empty()) + { + ERROR_LOG_FMT(VIDEO, "Failed to load custom pipeline action, must specify at least one pass"); + return nullptr; + } + + if (pipeline_passes.size() > 1) + { + ERROR_LOG_FMT( + VIDEO, + "Failed to load custom pipeline action, multiple passes are not currently supported"); + return nullptr; + } + + return std::make_unique(std::move(library), std::move(pipeline_passes)); +} + +CustomPipelineAction::CustomPipelineAction( + std::shared_ptr library, + std::vector pass_descriptions) + : m_library(std::move(library)), m_passes_config(std::move(pass_descriptions)) +{ + m_passes.resize(m_passes_config.size()); +} + +CustomPipelineAction::~CustomPipelineAction() = default; + +void CustomPipelineAction::OnDrawStarted(GraphicsModActionData::DrawStarted* draw_started) +{ + if (!draw_started) [[unlikely]] + return; + + if (!draw_started->custom_pixel_shader) [[unlikely]] + return; + + if (!m_valid) + return; + + if (m_passes.empty()) [[unlikely]] + return; + + // For now assume a single pass + auto& pass = m_passes[0]; + + if (!pass.m_pixel_shader.m_asset) [[unlikely]] + return; + + const auto shader_data = pass.m_pixel_shader.m_asset->GetData(); + if (shader_data) + { + if (pass.m_pixel_shader.m_asset->GetLastLoadedTime() > pass.m_pixel_shader.m_cached_write_time) + { + const auto material = pass.m_pixel_material.m_asset->GetData(); + if (!material) + return; + + pass.m_pixel_shader.m_cached_write_time = pass.m_pixel_shader.m_asset->GetLastLoadedTime(); + + for (const auto& prop : material->properties) + { + if (!shader_data->m_properties.contains(prop.m_code_name)) + { + ERROR_LOG_FMT(VIDEO, + "Custom pipeline has material asset '{}' that has property '{}'" + "that is not on shader asset '{}'", + pass.m_pixel_material.m_asset->GetAssetId(), prop.m_code_name, + pass.m_pixel_shader.m_asset->GetAssetId()); + return; + } + } + + // Calculate shader details + std::string color_shader_data = + ReplaceAll(shader_data->m_shader_source, "custom_main", CUSTOM_PIXELSHADER_COLOR_FUNC); + const auto global_conflicts = GlobalConflicts(color_shader_data); + color_shader_data = ReplaceAll(color_shader_data, "\r\n", "\n"); + color_shader_data = ReplaceAll(color_shader_data, "{", "{{"); + color_shader_data = ReplaceAll(color_shader_data, "}", "}}"); + // First replace global conflicts with dummy strings + // This avoids the problem where a shorter word + // is in a longer word, ex two functions: 'execute' and 'execute_fast' + for (std::size_t i = 0; i < global_conflicts.size(); i++) + { + const std::string& identifier = global_conflicts[i]; + color_shader_data = + ReplaceAll(color_shader_data, identifier, fmt::format("_{0}_DOLPHIN_TEMP_{0}_", i)); + } + // Now replace the temporaries with the actual value + for (std::size_t i = 0; i < global_conflicts.size(); i++) + { + const std::string& identifier = global_conflicts[i]; + color_shader_data = ReplaceAll(color_shader_data, fmt::format("_{0}_DOLPHIN_TEMP_{0}_", i), + fmt::format("{}_{{0}}", identifier)); + } + + for (const auto& texture_code_name : m_texture_code_names) + { + color_shader_data = + ReplaceAll(color_shader_data, fmt::format("{}_COORD", texture_code_name), + fmt::format("{}_COORD_{{0}}", texture_code_name)); + color_shader_data = ReplaceAll(color_shader_data, fmt::format("{}_UNIT", texture_code_name), + fmt::format("{}_UNIT_{{0}}", texture_code_name)); + } + + m_last_generated_shader_code = ShaderCode{}; + WriteDefines(&m_last_generated_shader_code, m_texture_code_names, draw_started->texture_unit); + m_last_generated_shader_code.Write("{}", color_shader_data); + } + CustomPixelShader custom_pixel_shader; + custom_pixel_shader.custom_shader = m_last_generated_shader_code.GetBuffer(); + *draw_started->custom_pixel_shader = custom_pixel_shader; + } +} + +void CustomPipelineAction::OnTextureCreate(GraphicsModActionData::TextureCreate* create) +{ + if (!create->custom_textures) [[unlikely]] + return; + + if (!create->additional_dependencies) [[unlikely]] + return; + + if (m_passes_config.empty()) [[unlikely]] + return; + + if (m_passes.empty()) [[unlikely]] + return; + + m_valid = true; + auto& loader = Core::System::GetInstance().GetCustomAssetLoader(); + + // For now assume a single pass + const auto& pass_config = m_passes_config[0]; + auto& pass = m_passes[0]; + + if (!pass.m_pixel_material.m_asset) + { + pass.m_pixel_material.m_asset = + loader.LoadMaterial(pass_config.m_pixel_material_asset, m_library); + pass.m_pixel_material.m_cached_write_time = pass.m_pixel_material.m_asset->GetLastLoadedTime(); + } + create->additional_dependencies->push_back(VideoCommon::CachedAsset{ + pass.m_pixel_material.m_asset, pass.m_pixel_material.m_asset->GetLastLoadedTime()}); + + const auto material_data = pass.m_pixel_material.m_asset->GetData(); + if (!material_data) + return; + + if (!pass.m_pixel_shader.m_asset || pass.m_pixel_material.m_asset->GetLastLoadedTime() > + pass.m_pixel_material.m_cached_write_time) + { + pass.m_pixel_shader.m_asset = loader.LoadPixelShader(material_data->shader_asset, m_library); + // Note: the asset timestamp will be updated in the draw command + } + create->additional_dependencies->push_back(VideoCommon::CachedAsset{ + pass.m_pixel_shader.m_asset, pass.m_pixel_shader.m_asset->GetLastLoadedTime()}); + + m_texture_code_names.clear(); + std::vector> game_assets; + for (const auto& property : material_data->properties) + { + if (property.m_type == VideoCommon::MaterialProperty::Type::Type_TextureAsset) + { + if (property.m_value) + { + if (auto* value = std::get_if(&*property.m_value)) + { + auto asset = loader.LoadGameTexture(*value, m_library); + if (asset) + { + const auto loaded_time = asset->GetLastLoadedTime(); + game_assets.push_back(VideoCommon::CachedAsset{ + std::move(asset), loaded_time}); + m_texture_code_names.push_back(property.m_code_name); + } + } + } + } + } + // Note: we swap here instead of doing a clear + append of the member + // variable so that any loaded assets from previous iterations + // won't be let go + std::swap(pass.m_game_textures, game_assets); + + for (auto& game_texture : pass.m_game_textures) + { + if (game_texture.m_asset) + { + auto data = game_texture.m_asset->GetData(); + if (data) + { + if (create->texture_width != data->m_levels[0].width || + create->texture_height != data->m_levels[0].height) + { + ERROR_LOG_FMT(VIDEO, + "Custom pipeline for texture '{}' has asset '{}' that does not match " + "the width/height of the texture loaded. Texture {}x{} vs asset {}x{}", + create->texture_name, game_texture.m_asset->GetAssetId(), + create->texture_width, create->texture_height, data->m_levels[0].width, + data->m_levels[0].height); + m_valid = false; + } + } + else + { + m_valid = false; + } + } + } + + // TODO: compare game textures and shader requirements + + create->custom_textures->insert(create->custom_textures->end(), pass.m_game_textures.begin(), + pass.m_game_textures.end()); +} diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.h new file mode 100644 index 0000000000..6de0af8afe --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.h @@ -0,0 +1,53 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include + +#include + +#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 Create(const picojson::value& json_data, + std::string_view path); + CustomPipelineAction(std::shared_ptr library, + std::vector pass_descriptions); + ~CustomPipelineAction(); + void OnDrawStarted(GraphicsModActionData::DrawStarted*) override; + void OnTextureCreate(GraphicsModActionData::TextureCreate*) override; + +private: + std::shared_ptr m_library; + std::vector m_passes_config; + struct PipelinePass + { + VideoCommon::CachedAsset m_pixel_material; + VideoCommon::CachedAsset m_pixel_shader; + std::vector> m_game_textures; + }; + std::vector m_passes; + + ShaderCode m_last_generated_shader_code; + + bool m_valid = true; + + std::vector m_texture_code_names; +}; diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.cpp index 7b97155d8a..4055265e5a 100644 --- a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.cpp +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.cpp @@ -3,6 +3,7 @@ #include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.h" +#include "VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.h" #include "VideoCommon/GraphicsModSystem/Runtime/Actions/MoveAction.h" #include "VideoCommon/GraphicsModSystem/Runtime/Actions/PrintAction.h" #include "VideoCommon/GraphicsModSystem/Runtime/Actions/ScaleAction.h" @@ -29,6 +30,10 @@ std::unique_ptr Create(std::string_view name, const picojson: { return ScaleAction::Create(json_data); } + else if (name == "custom_pipeline") + { + return CustomPipelineAction::Create(json_data, path); + } return nullptr; } From 675544ec2beb6ba8c3d766b3dfe52e6e8cc30c0d Mon Sep 17 00:00:00 2001 From: iwubcode Date: Fri, 30 Dec 2022 01:30:07 -0600 Subject: [PATCH 09/12] docs: Add custom pipeline documentation --- docs/CustomPipelineGraphicsMod.md | 250 ++++++++++++++++++++++++++++++ 1 file changed, 250 insertions(+) create mode 100644 docs/CustomPipelineGraphicsMod.md diff --git a/docs/CustomPipelineGraphicsMod.md b/docs/CustomPipelineGraphicsMod.md new file mode 100644 index 0000000000..c2f49e772a --- /dev/null +++ b/docs/CustomPipelineGraphicsMod.md @@ -0,0 +1,250 @@ +# 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 | + +`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); +} +``` From 931a8aa4139bddbc59a9cf94ca8e15f05000960c Mon Sep 17 00:00:00 2001 From: iwubcode Date: Sat, 7 Jan 2023 12:30:29 -0600 Subject: [PATCH 10/12] VideoCommon: add milliseconds elapsed time value to pixel shaders as a uniform to be able to support animation effects in custom shaders --- Source/Core/VideoCommon/ConstantManager.h | 2 ++ Source/Core/VideoCommon/PixelShaderGen.cpp | 4 ++++ Source/Core/VideoCommon/ShaderGenCommon.cpp | 1 + Source/Core/VideoCommon/UberShaderPixel.cpp | 3 +++ Source/Core/VideoCommon/VertexManagerBase.cpp | 10 ++++++++++ Source/Core/VideoCommon/VertexManagerBase.h | 2 ++ docs/CustomPipelineGraphicsMod.md | 3 ++- 7 files changed, 24 insertions(+), 1 deletion(-) diff --git a/Source/Core/VideoCommon/ConstantManager.h b/Source/Core/VideoCommon/ConstantManager.h index 88c25a9823..b8c65aaefb 100644 --- a/Source/Core/VideoCommon/ConstantManager.h +++ b/Source/Core/VideoCommon/ConstantManager.h @@ -58,6 +58,8 @@ struct alignas(16) PixelShaderConstants // For shader_framebuffer_fetch logic ops: u32 logic_op_enable; // bool LogicOp logic_op_mode; + // For custom shaders... + u32 time_ms; }; struct alignas(16) VertexShaderConstants diff --git a/Source/Core/VideoCommon/PixelShaderGen.cpp b/Source/Core/VideoCommon/PixelShaderGen.cpp index 326cd975d9..69fdc36efc 100644 --- a/Source/Core/VideoCommon/PixelShaderGen.cpp +++ b/Source/Core/VideoCommon/PixelShaderGen.cpp @@ -406,6 +406,7 @@ void WritePixelShaderCommonHeader(ShaderCode& out, APIType api_type, "\tbool blend_subtract_alpha;\n" "\tbool logic_op_enable;\n" "\tuint logic_op_mode;\n" + "\tuint time_ms;\n" "}};\n\n"); out.Write("#define bpmem_combiners(i) (bpmem_pack1[(i)].xy)\n" "#define bpmem_tevind(i) (bpmem_pack1[(i)].z)\n" @@ -869,6 +870,9 @@ void WriteCustomShaderStructImpl(ShaderCode* out, u32 num_stages, bool per_pixel // Actual data will be filled out in the tev stage code, just set the // stage count for now out->Write("\tcustom_data.tev_stage_count = {};\n", num_stages); + + // Time + out->Write("\tcustom_data.time_ms = time_ms;\n"); } static void WriteStage(ShaderCode& out, const pixel_shader_uid_data* uid_data, int n, diff --git a/Source/Core/VideoCommon/ShaderGenCommon.cpp b/Source/Core/VideoCommon/ShaderGenCommon.cpp index 5f2b2b224f..e4922f3e3e 100644 --- a/Source/Core/VideoCommon/ShaderGenCommon.cpp +++ b/Source/Core/VideoCommon/ShaderGenCommon.cpp @@ -447,5 +447,6 @@ void WriteCustomShaderStructDef(ShaderCode* out, u32 numtexgens) out->Write("\tCustomShaderTevStage[16] tev_stages;\n"); out->Write("\tuint tev_stage_count;\n"); out->Write("\tfloat4 final_color;\n"); + out->Write("\tuint time_ms;\n"); out->Write("}};\n\n"); } diff --git a/Source/Core/VideoCommon/UberShaderPixel.cpp b/Source/Core/VideoCommon/UberShaderPixel.cpp index 278be9c082..bcddde4adc 100644 --- a/Source/Core/VideoCommon/UberShaderPixel.cpp +++ b/Source/Core/VideoCommon/UberShaderPixel.cpp @@ -266,6 +266,9 @@ void WriteCustomShaderStructImpl(ShaderCode* out, u32 num_texgen, bool per_pixel // Actual data will be filled out in the tev stage code, just set the // stage count for now out->Write("\tcustom_data.tev_stage_count = num_stages;\n"); + + // Time + out->Write("\tcustom_data.time_ms = time_ms;\n"); } } // namespace PixelShaderUid GetPixelShaderUid() diff --git a/Source/Core/VideoCommon/VertexManagerBase.cpp b/Source/Core/VideoCommon/VertexManagerBase.cpp index 92e5be9952..564eb684dc 100644 --- a/Source/Core/VideoCommon/VertexManagerBase.cpp +++ b/Source/Core/VideoCommon/VertexManagerBase.cpp @@ -15,6 +15,7 @@ #include "Core/ConfigManager.h" #include "Core/DolphinAnalytics.h" +#include "Core/HW/SystemTimers.h" #include "Core/System.h" #include "VideoCommon/AbstractGfx.h" @@ -107,6 +108,8 @@ VertexManagerBase::~VertexManagerBase() = default; bool VertexManagerBase::Initialize() { m_frame_end_event = AfterFrameEvent::Register([this] { OnEndFrame(); }, "VertexManagerBase"); + m_after_present_event = AfterPresentEvent::Register( + [this](PresentInfo& pi) { m_ticks_elapsed = pi.emulated_timestamp; }, "VertexManagerBase"); m_index_generator.Init(); m_custom_shader_cache = std::make_unique(); m_cpu_cull.Init(); @@ -526,6 +529,13 @@ void VertexManagerBase::Flush() auto& geometry_shader_manager = system.GetGeometryShaderManager(); auto& vertex_shader_manager = system.GetVertexShaderManager(); + if (g_ActiveConfig.bGraphicMods) + { + const double seconds_elapsed = + static_cast(m_ticks_elapsed) / SystemTimers::GetTicksPerSecond(); + pixel_shader_manager.constants.time_ms = seconds_elapsed * 1000; + } + CalculateBinormals(VertexLoaderManager::GetCurrentVertexFormat()); // Calculate ZSlope for zfreeze const auto used_textures = UsedTextures(); diff --git a/Source/Core/VideoCommon/VertexManagerBase.h b/Source/Core/VideoCommon/VertexManagerBase.h index 66e24cbf36..ebb857ff21 100644 --- a/Source/Core/VideoCommon/VertexManagerBase.h +++ b/Source/Core/VideoCommon/VertexManagerBase.h @@ -233,8 +233,10 @@ private: bool m_allow_background_execution = true; std::unique_ptr m_custom_shader_cache; + u64 m_ticks_elapsed; Common::EventHook m_frame_end_event; + Common::EventHook m_after_present_event; }; extern std::unique_ptr g_vertex_manager; diff --git a/docs/CustomPipelineGraphicsMod.md b/docs/CustomPipelineGraphicsMod.md index c2f49e772a..771d3dba36 100644 --- a/docs/CustomPipelineGraphicsMod.md +++ b/docs/CustomPipelineGraphicsMod.md @@ -105,6 +105,7 @@ vec4 custom_main( in CustomShaderData data ) |``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: @@ -247,4 +248,4 @@ vec4 custom_main( in CustomShaderData data ) } return vec4(total_diffuse * vec3(0, 0, 1), 1); } -``` +``` \ No newline at end of file From 6ea0d178026f105bc4a27344bb6b867aac6109e0 Mon Sep 17 00:00:00 2001 From: iwubcode Date: Thu, 9 Feb 2023 19:39:40 -0600 Subject: [PATCH 11/12] VideoCommon: when graphics settings change, trigger a reload of all custom shaders --- Source/Core/VideoCommon/VideoConfig.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Core/VideoCommon/VideoConfig.cpp b/Source/Core/VideoCommon/VideoConfig.cpp index 83dc5eb53e..02db6db9f7 100644 --- a/Source/Core/VideoCommon/VideoConfig.cpp +++ b/Source/Core/VideoCommon/VideoConfig.cpp @@ -353,6 +353,7 @@ void CheckForConfigChanges() { OSD::AddMessage("Video config changed, reloading shaders.", OSD::Duration::NORMAL); g_vertex_manager->InvalidatePipelineObject(); + g_vertex_manager->NotifyCustomShaderCacheOfHostChange(new_host_config); g_shader_cache->SetHostConfig(new_host_config); g_shader_cache->Reload(); g_framebuffer_manager->RecompileShaders(); From 55061216855af89f21eee1174fa81d927bc94991 Mon Sep 17 00:00:00 2001 From: iwubcode Date: Mon, 10 Jul 2023 22:23:32 -0500 Subject: [PATCH 12/12] VideoCommon: add support to graphics mod manager to load in assets and pass it to graphics actions --- Source/Core/DolphinLib.props | 2 + Source/Core/VideoCommon/CMakeLists.txt | 2 + .../GraphicsModSystem/Config/GraphicsMod.cpp | 21 ++++++++ .../GraphicsModSystem/Config/GraphicsMod.h | 2 + .../Config/GraphicsModAsset.cpp | 52 +++++++++++++++++++ .../Config/GraphicsModAsset.h | 18 +++++++ .../Runtime/Actions/CustomPipelineAction.h | 5 +- .../Runtime/GraphicsModActionFactory.cpp | 4 +- .../Runtime/GraphicsModActionFactory.h | 3 +- .../Runtime/GraphicsModManager.cpp | 38 +++++++++++--- 10 files changed, 136 insertions(+), 11 deletions(-) create mode 100644 Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.cpp create mode 100644 Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.h diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index b8c7162114..967b6ac9ad 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -663,6 +663,7 @@ + @@ -1278,6 +1279,7 @@ + diff --git a/Source/Core/VideoCommon/CMakeLists.txt b/Source/Core/VideoCommon/CMakeLists.txt index 99915a0023..46f1955524 100644 --- a/Source/Core/VideoCommon/CMakeLists.txt +++ b/Source/Core/VideoCommon/CMakeLists.txt @@ -64,6 +64,8 @@ add_library(videocommon GeometryShaderManager.h GraphicsModSystem/Config/GraphicsMod.cpp GraphicsModSystem/Config/GraphicsMod.h + GraphicsModSystem/Config/GraphicsModAsset.cpp + GraphicsModSystem/Config/GraphicsModAsset.h GraphicsModSystem/Config/GraphicsModFeature.cpp GraphicsModSystem/Config/GraphicsModFeature.h GraphicsModSystem/Config/GraphicsModGroup.cpp diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.cpp index c7dc5aab87..b04190ad67 100644 --- a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.cpp +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.cpp @@ -178,6 +178,27 @@ bool GraphicsModConfig::DeserializeFromConfig(const picojson::value& value) } } + const auto& assets = value.get("assets"); + if (assets.is()) + { + for (const auto& asset_val : assets.get()) + { + if (!asset_val.is()) + { + ERROR_LOG_FMT( + VIDEO, "Failed to load mod configuration file, specified asset is not a json object"); + return false; + } + GraphicsModAssetConfig asset; + if (!asset.DeserializeFromConfig(asset_val.get())) + { + return false; + } + + m_assets.push_back(std::move(asset)); + } + } + return true; } diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.h b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.h index f4f6859cb3..953af6201b 100644 --- a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.h +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.h @@ -9,6 +9,7 @@ #include +#include "VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.h" #include "VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.h" #include "VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.h" @@ -30,6 +31,7 @@ struct GraphicsModConfig std::vector m_groups; std::vector m_features; + std::vector m_assets; static std::optional Create(const std::string& file, Source source); static std::optional Create(const picojson::object* obj); diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.cpp new file mode 100644 index 0000000000..fb5572b3da --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.cpp @@ -0,0 +1,52 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.h" + +#include "Common/Logging/Log.h" + +bool GraphicsModAssetConfig::DeserializeFromConfig(const picojson::object& obj) +{ + auto name_iter = obj.find("name"); + if (name_iter == obj.end()) + { + ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, specified asset has no name"); + return false; + } + if (!name_iter->second.is()) + { + ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, specified asset has a name " + "that is not a string"); + return false; + } + m_name = name_iter->second.to_str(); + + auto data_iter = obj.find("data"); + if (data_iter == obj.end()) + { + ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, specified asset '{}' has no data", + m_name); + return false; + } + if (!data_iter->second.is()) + { + ERROR_LOG_FMT(VIDEO, + "Failed to load mod configuration file, specified asset '{}' has data " + "that is not an object", + m_name); + return false; + } + for (const auto& [key, value] : data_iter->second.get()) + { + if (!value.is()) + { + ERROR_LOG_FMT(VIDEO, + "Failed to load mod configuration file, specified asset '{}' has data " + "with a value for key '{}' that is not a string", + m_name, key); + } + m_map[key] = value.to_str(); + } + + return true; +} diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.h b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.h new file mode 100644 index 0000000000..b38ba792cc --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.h @@ -0,0 +1,18 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include + +#include "VideoCommon/Assets/DirectFilesystemAssetLibrary.h" + +struct GraphicsModAssetConfig +{ + std::string m_name; + VideoCommon::DirectFilesystemAssetLibrary::AssetMap m_map; + + bool DeserializeFromConfig(const picojson::object& obj); +}; diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.h index 6de0af8afe..4760da3124 100644 --- a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.h +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.h @@ -26,8 +26,9 @@ public: std::string m_pixel_material_asset; }; - static std::unique_ptr Create(const picojson::value& json_data, - std::string_view path); + static std::unique_ptr + Create(const picojson::value& json_data, + std::shared_ptr library); CustomPipelineAction(std::shared_ptr library, std::vector pass_descriptions); ~CustomPipelineAction(); diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.cpp index 4055265e5a..6ff64aa038 100644 --- a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.cpp +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.cpp @@ -12,7 +12,7 @@ namespace GraphicsModActionFactory { std::unique_ptr Create(std::string_view name, const picojson::value& json_data, - std::string_view path) + std::shared_ptr library) { if (name == "print") { @@ -32,7 +32,7 @@ std::unique_ptr Create(std::string_view name, const picojson: } else if (name == "custom_pipeline") { - return CustomPipelineAction::Create(json_data, path); + return CustomPipelineAction::Create(json_data, std::move(library)); } return nullptr; diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.h index 7709d75617..069533d6d1 100644 --- a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.h +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.h @@ -8,10 +8,11 @@ #include +#include "VideoCommon/Assets/CustomAssetLibrary.h" #include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h" namespace GraphicsModActionFactory { std::unique_ptr Create(std::string_view name, const picojson::value& json_data, - std::string_view path); + std::shared_ptr library); } diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.cpp index 3e4cd6e4e6..0d2f0fe347 100644 --- a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.cpp +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.cpp @@ -13,7 +13,9 @@ #include "Core/ConfigManager.h" +#include "VideoCommon/Assets/DirectFilesystemAssetLibrary.h" #include "VideoCommon/GraphicsModSystem/Config/GraphicsMod.h" +#include "VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.h" #include "VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.h" #include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.h" #include "VideoCommon/TextureInfo.h" @@ -187,6 +189,8 @@ void GraphicsModManager::Load(const GraphicsModGroupConfig& config) const auto& mods = config.GetMods(); + auto filesystem_library = std::make_shared(); + std::map> group_to_targets; for (const auto& mod : mods) { @@ -208,6 +212,29 @@ void GraphicsModManager::Load(const GraphicsModGroupConfig& config) group_to_targets[internal_group].push_back(target); } } + + std::string base_path; + SplitPath(mod.GetAbsolutePath(), &base_path, nullptr, nullptr); + for (const GraphicsModAssetConfig& asset : mod.m_assets) + { + auto asset_map = asset.m_map; + for (auto& [k, v] : asset_map) + { + if (v.is_absolute()) + { + WARN_LOG_FMT(VIDEO, + "Specified graphics mod asset '{}' for mod '{}' has an absolute path, you " + "shouldn't release this to users.", + asset.m_name, mod.m_title); + } + else + { + v = std::filesystem::path{base_path} / v; + } + } + + filesystem_library->SetAssetIDMapData(asset.m_name, std::move(asset_map)); + } } for (const auto& mod : mods) @@ -215,12 +242,11 @@ void GraphicsModManager::Load(const GraphicsModGroupConfig& config) for (const GraphicsModFeatureConfig& feature : mod.m_features) { const auto create_action = - [](const std::string_view& action_name, const picojson::value& json_data, - GraphicsModConfig mod_config) -> std::unique_ptr { - std::string base_path; - SplitPath(mod_config.GetAbsolutePath(), &base_path, nullptr, nullptr); - - auto action = GraphicsModActionFactory::Create(action_name, json_data, base_path); + [filesystem_library](const std::string_view& action_name, + const picojson::value& json_data, + GraphicsModConfig mod_config) -> std::unique_ptr { + auto action = + GraphicsModActionFactory::Create(action_name, json_data, std::move(filesystem_library)); if (action == nullptr) { return nullptr;