diff --git a/.gitmodules b/.gitmodules index e6e29cddda..b62f9696f8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -87,3 +87,6 @@ [submodule "Externals/SFML/SFML"] path = Externals/SFML/SFML url = https://github.com/SFML/SFML.git +[submodule "Externals/watcher/watcher"] + path = Externals/watcher/watcher + url = https://github.com/e-dant/watcher diff --git a/CMakeLists.txt b/CMakeLists.txt index f2fcabd64f..27ee501b40 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -768,6 +768,8 @@ if (USE_RETRO_ACHIEVEMENTS) add_subdirectory(Externals/rcheevos) endif() +add_subdirectory(Externals/watcher) + ######################################## # Pre-build events: Define configuration variables and write SCM info header # diff --git a/Data/Sys/GraphicsModEditor/Pipelines/highlight/color.ps.glsl b/Data/Sys/GraphicsModEditor/Pipelines/highlight/color.ps.glsl new file mode 100644 index 0000000000..018a9d4dc8 --- /dev/null +++ b/Data/Sys/GraphicsModEditor/Pipelines/highlight/color.ps.glsl @@ -0,0 +1,8 @@ +void fragment(in DolphinFragmentInput frag_input, out DolphinFragmentOutput frag_output) +{ + dolphin_emulated_fragment(frag_input, frag_output); + vec3 tint_color = vec3(0.0, 0.0, 1.0); + vec3 input_color = frag_output.main.rgb / 255.0; + vec3 output_color = (0.5 * input_color) + (0.5 * tint_color.rgb); + frag_output.main.rgb = ivec3(output_color * 255); +} diff --git a/Data/Sys/GraphicsModEditor/Pipelines/highlight/color.rastershader b/Data/Sys/GraphicsModEditor/Pipelines/highlight/color.rastershader new file mode 100644 index 0000000000..8df1d3a7c1 --- /dev/null +++ b/Data/Sys/GraphicsModEditor/Pipelines/highlight/color.rastershader @@ -0,0 +1,6 @@ +{ + "pixel_properties":[], + "pixel_output_targets":[], + "pixel_samplers":[], + "vertex_properties":[] +} \ No newline at end of file diff --git a/Data/Sys/GraphicsModEditor/Pipelines/highlight/color.vs.glsl b/Data/Sys/GraphicsModEditor/Pipelines/highlight/color.vs.glsl new file mode 100644 index 0000000000..e926b3b611 --- /dev/null +++ b/Data/Sys/GraphicsModEditor/Pipelines/highlight/color.vs.glsl @@ -0,0 +1,4 @@ +void vertex(in DolphinVertexInput vertex_input, out DolphinVertexOutput vertex_output) +{ + dolphin_emulated_vertex(vertex_input, vertex_output); +} \ No newline at end of file diff --git a/Data/Sys/GraphicsModEditor/Pipelines/highlight/highlight.rastermaterial b/Data/Sys/GraphicsModEditor/Pipelines/highlight/highlight.rastermaterial new file mode 100644 index 0000000000..57145caab3 --- /dev/null +++ b/Data/Sys/GraphicsModEditor/Pipelines/highlight/highlight.rastermaterial @@ -0,0 +1,8 @@ +{ + "next_material_asset":"", + "pixel_properties":[], + "pixel_textures":[], + "render_targets":[], + "shader_asset": "highlight_shader", + "vertex_properties":[] +} \ No newline at end of file diff --git a/Data/Sys/GraphicsModEditor/Pipelines/light_visualization/simple-light-visualization.ps.glsl b/Data/Sys/GraphicsModEditor/Pipelines/light_visualization/simple-light-visualization.ps.glsl new file mode 100644 index 0000000000..fc0f78b0d8 --- /dev/null +++ b/Data/Sys/GraphicsModEditor/Pipelines/light_visualization/simple-light-visualization.ps.glsl @@ -0,0 +1,12 @@ +void fragment(in DolphinFragmentInput frag_input, out DolphinFragmentOutput frag_output) +{ + if (frag_input.normal.xyz == vec3(0, 0, 0)) + { + dolphin_emulated_fragment(frag_input, frag_output); + } + else + { + vec4 output_color = dolphin_calculate_lighting_chn0(frag_input.color_0, vec4(frag_input.position, 1), frag_input.normal); + frag_output.main = ivec4(output_color * 255.0); + } +} diff --git a/Data/Sys/GraphicsModEditor/Pipelines/light_visualization/simple-light-visualization.rastermaterial b/Data/Sys/GraphicsModEditor/Pipelines/light_visualization/simple-light-visualization.rastermaterial new file mode 100644 index 0000000000..206d6fcb3a --- /dev/null +++ b/Data/Sys/GraphicsModEditor/Pipelines/light_visualization/simple-light-visualization.rastermaterial @@ -0,0 +1,8 @@ +{ + "next_material_asset":"", + "pixel_properties":[], + "pixel_textures":[], + "render_targets":[], + "shader_asset": "simple_light_visualization_shader", + "vertex_properties":[] +} \ No newline at end of file diff --git a/Data/Sys/GraphicsModEditor/Pipelines/light_visualization/simple-light-visualization.rastershader b/Data/Sys/GraphicsModEditor/Pipelines/light_visualization/simple-light-visualization.rastershader new file mode 100644 index 0000000000..8df1d3a7c1 --- /dev/null +++ b/Data/Sys/GraphicsModEditor/Pipelines/light_visualization/simple-light-visualization.rastershader @@ -0,0 +1,6 @@ +{ + "pixel_properties":[], + "pixel_output_targets":[], + "pixel_samplers":[], + "vertex_properties":[] +} \ No newline at end of file diff --git a/Data/Sys/GraphicsModEditor/Pipelines/light_visualization/simple-light-visualization.vs.glsl b/Data/Sys/GraphicsModEditor/Pipelines/light_visualization/simple-light-visualization.vs.glsl new file mode 100644 index 0000000000..e926b3b611 --- /dev/null +++ b/Data/Sys/GraphicsModEditor/Pipelines/light_visualization/simple-light-visualization.vs.glsl @@ -0,0 +1,4 @@ +void vertex(in DolphinVertexInput vertex_input, out DolphinVertexOutput vertex_output) +{ + dolphin_emulated_vertex(vertex_input, vertex_output); +} \ No newline at end of file diff --git a/Data/Sys/GraphicsModEditor/Pipelines/normal_visualization/normal-visualization.ps.glsl b/Data/Sys/GraphicsModEditor/Pipelines/normal_visualization/normal-visualization.ps.glsl new file mode 100644 index 0000000000..bf13d6d4e5 --- /dev/null +++ b/Data/Sys/GraphicsModEditor/Pipelines/normal_visualization/normal-visualization.ps.glsl @@ -0,0 +1,12 @@ +void fragment(in DolphinFragmentInput frag_input, out DolphinFragmentOutput frag_output) +{ + if (frag_input.normal.xyz == vec3(0, 0, 0)) + { + dolphin_emulated_fragment(frag_input, frag_output); + } + else + { + vec4 output_color = vec4(frag_input.normal.xyz * 0.5 + 0.5, 1); + frag_output.main = ivec4(output_color * 255.0); + } +} diff --git a/Data/Sys/GraphicsModEditor/Pipelines/normal_visualization/normal-visualization.rastermaterial b/Data/Sys/GraphicsModEditor/Pipelines/normal_visualization/normal-visualization.rastermaterial new file mode 100644 index 0000000000..ff7628d542 --- /dev/null +++ b/Data/Sys/GraphicsModEditor/Pipelines/normal_visualization/normal-visualization.rastermaterial @@ -0,0 +1,8 @@ +{ + "next_material_asset":"", + "pixel_properties":[], + "pixel_textures":[], + "render_targets":[], + "shader_asset": "normal_visualization_shader", + "vertex_properties":[] +} \ No newline at end of file diff --git a/Data/Sys/GraphicsModEditor/Pipelines/normal_visualization/normal-visualization.rastershader b/Data/Sys/GraphicsModEditor/Pipelines/normal_visualization/normal-visualization.rastershader new file mode 100644 index 0000000000..8df1d3a7c1 --- /dev/null +++ b/Data/Sys/GraphicsModEditor/Pipelines/normal_visualization/normal-visualization.rastershader @@ -0,0 +1,6 @@ +{ + "pixel_properties":[], + "pixel_output_targets":[], + "pixel_samplers":[], + "vertex_properties":[] +} \ No newline at end of file diff --git a/Data/Sys/GraphicsModEditor/Pipelines/normal_visualization/normal-visualization.vs.glsl b/Data/Sys/GraphicsModEditor/Pipelines/normal_visualization/normal-visualization.vs.glsl new file mode 100644 index 0000000000..e926b3b611 --- /dev/null +++ b/Data/Sys/GraphicsModEditor/Pipelines/normal_visualization/normal-visualization.vs.glsl @@ -0,0 +1,4 @@ +void vertex(in DolphinVertexInput vertex_input, out DolphinVertexOutput vertex_output) +{ + dolphin_emulated_vertex(vertex_input, vertex_output); +} \ No newline at end of file diff --git a/Data/Sys/GraphicsModEditor/Templates/raster_material.json b/Data/Sys/GraphicsModEditor/Templates/raster_material.json new file mode 100644 index 0000000000..a7f09b833c --- /dev/null +++ b/Data/Sys/GraphicsModEditor/Templates/raster_material.json @@ -0,0 +1,8 @@ +{ + "shader_asset": "", + "pixel_properties": [], + "vertex_properties": [], + "next_material_asset": "", + "pixel_textures": [], + "render_targets": [] +} diff --git a/Data/Sys/GraphicsModEditor/Templates/raster_shader.json b/Data/Sys/GraphicsModEditor/Templates/raster_shader.json new file mode 100644 index 0000000000..771e24b273 --- /dev/null +++ b/Data/Sys/GraphicsModEditor/Templates/raster_shader.json @@ -0,0 +1,6 @@ +{ + "pixel_properties": [], + "pixel_output_targets": [], + "pixel_samplers": [], + "vertex_properties": [] +} \ No newline at end of file diff --git a/Data/Sys/GraphicsModEditor/Templates/raster_shader.ps.glsl b/Data/Sys/GraphicsModEditor/Templates/raster_shader.ps.glsl new file mode 100644 index 0000000000..cebadf0254 --- /dev/null +++ b/Data/Sys/GraphicsModEditor/Templates/raster_shader.ps.glsl @@ -0,0 +1,4 @@ +void fragment(in DolphinFragmentInput frag_input, out DolphinFragmentOutput frag_output) +{ + dolphin_emulated_fragment(frag_input, frag_output); +} diff --git a/Data/Sys/GraphicsModEditor/Templates/raster_shader.vs.glsl b/Data/Sys/GraphicsModEditor/Templates/raster_shader.vs.glsl new file mode 100644 index 0000000000..42a336b1c7 --- /dev/null +++ b/Data/Sys/GraphicsModEditor/Templates/raster_shader.vs.glsl @@ -0,0 +1,4 @@ +void vertex(in DolphinVertexInput vertex_input, out DolphinVertexOutput vertex_output) +{ + dolphin_emulated_vertex(vertex_input, vertex_output); +} diff --git a/Data/Sys/GraphicsModEditor/Textures/icons8-code-file-100.png b/Data/Sys/GraphicsModEditor/Textures/icons8-code-file-100.png new file mode 100644 index 0000000000..120f95f71f Binary files /dev/null and b/Data/Sys/GraphicsModEditor/Textures/icons8-code-file-100.png differ diff --git a/Data/Sys/GraphicsModEditor/Textures/icons8-cube-filled-50.png b/Data/Sys/GraphicsModEditor/Textures/icons8-cube-filled-50.png new file mode 100644 index 0000000000..a3f93e3650 Binary files /dev/null and b/Data/Sys/GraphicsModEditor/Textures/icons8-cube-filled-50.png differ diff --git a/Data/Sys/GraphicsModEditor/Textures/icons8-document-500.png b/Data/Sys/GraphicsModEditor/Textures/icons8-document-500.png new file mode 100644 index 0000000000..440e7e82d2 Binary files /dev/null and b/Data/Sys/GraphicsModEditor/Textures/icons8-document-500.png differ diff --git a/Data/Sys/GraphicsModEditor/Textures/icons8-error-50.png b/Data/Sys/GraphicsModEditor/Textures/icons8-error-50.png new file mode 100644 index 0000000000..41ec163909 Binary files /dev/null and b/Data/Sys/GraphicsModEditor/Textures/icons8-error-50.png differ diff --git a/Data/Sys/GraphicsModEditor/Textures/icons8-file-500.png b/Data/Sys/GraphicsModEditor/Textures/icons8-file-500.png new file mode 100644 index 0000000000..36a08150db Binary files /dev/null and b/Data/Sys/GraphicsModEditor/Textures/icons8-file-500.png differ diff --git a/Data/Sys/GraphicsModEditor/Textures/icons8-folder-50.png b/Data/Sys/GraphicsModEditor/Textures/icons8-folder-50.png new file mode 100644 index 0000000000..97b4f61d23 Binary files /dev/null and b/Data/Sys/GraphicsModEditor/Textures/icons8-folder-50.png differ diff --git a/Data/Sys/GraphicsModEditor/Textures/icons8-folder-500.png b/Data/Sys/GraphicsModEditor/Textures/icons8-folder-500.png new file mode 100644 index 0000000000..978d7abc03 Binary files /dev/null and b/Data/Sys/GraphicsModEditor/Textures/icons8-folder-500.png differ diff --git a/Data/Sys/GraphicsModEditor/Textures/icons8-image-file-500.png b/Data/Sys/GraphicsModEditor/Textures/icons8-image-file-500.png new file mode 100644 index 0000000000..7b053e610a Binary files /dev/null and b/Data/Sys/GraphicsModEditor/Textures/icons8-image-file-500.png differ diff --git a/Data/Sys/GraphicsModEditor/Textures/icons8-portraits-50.png b/Data/Sys/GraphicsModEditor/Textures/icons8-portraits-50.png new file mode 100644 index 0000000000..b26fb1603e Binary files /dev/null and b/Data/Sys/GraphicsModEditor/Textures/icons8-portraits-50.png differ diff --git a/Data/Sys/GraphicsModEditor/Textures/icons8-search-50.png b/Data/Sys/GraphicsModEditor/Textures/icons8-search-50.png new file mode 100644 index 0000000000..a10c674050 Binary files /dev/null and b/Data/Sys/GraphicsModEditor/Textures/icons8-search-50.png differ diff --git a/Data/Sys/Load/GraphicMods/All Games Bloom Removal/metadata.json b/Data/Sys/Load/GraphicMods/All Games Bloom Removal/metadata.json index 1dd6c8dcc1..122cc1f916 100644 --- a/Data/Sys/Load/GraphicMods/All Games Bloom Removal/metadata.json +++ b/Data/Sys/Load/GraphicMods/All Games Bloom Removal/metadata.json @@ -1,15 +1,22 @@ { - "meta": - { - "title": "Bloom Removal", - "author": "Dolphin Team", - "description": "Skips drawing bloom effects. May be preferable when using a bloom solution from Dolphin's post processing shaders or a third party tool." - }, - "features": - [ - { - "group": "Bloom", - "action": "skip" - } - ] -} \ No newline at end of file + "actions": [ + { + "data": {}, + "factory_name": "skip" + } + ], + "assets": [ + ], + "meta": { + "author": "Dolphin Team", + "description": "Skips drawing bloom effects. May be preferable when using a bloom solution from Dolphin's post processing shaders or a third party tool.", + "mod_version": "1.0.0", + "schema_version": 1, + "title": "Bloom Removal" + }, + "tag_to_actions": { + "Bloom": [ + 0 + ] + } +} diff --git a/Data/Sys/Load/GraphicMods/All Games DOF Removal/metadata.json b/Data/Sys/Load/GraphicMods/All Games DOF Removal/metadata.json index 5758d41b27..174b299e4f 100644 --- a/Data/Sys/Load/GraphicMods/All Games DOF Removal/metadata.json +++ b/Data/Sys/Load/GraphicMods/All Games DOF Removal/metadata.json @@ -1,15 +1,22 @@ { - "meta": - { - "title": "DOF Removal", - "author": "Dolphin Team", - "description": "Skips drawing DOF effects. May be preferable when using a DOF solution from Dolphin's post processing shaders or a third party tool." - }, - "features": - [ - { - "group": "DOF", - "action": "skip" - } - ] + "actions": [ + { + "data": {}, + "factory_name": "skip" + } + ], + "assets": [ + ], + "meta": { + "author": "Dolphin Team", + "description": "Skips drawing DOF effects. May be preferable when using a DOF solution from Dolphin's post processing shaders or a third party tool.", + "mod_version": "1.0.0", + "schema_version": 1, + "title": "DOF Removal" + }, + "tag_to_actions": { + "Depth of Field": [ + 0 + ] + } } diff --git a/Data/Sys/Load/GraphicMods/All Games HUD Removal/metadata.json b/Data/Sys/Load/GraphicMods/All Games HUD Removal/metadata.json index ac08648ef4..a0014ecbb0 100644 --- a/Data/Sys/Load/GraphicMods/All Games HUD Removal/metadata.json +++ b/Data/Sys/Load/GraphicMods/All Games HUD Removal/metadata.json @@ -1,14 +1,22 @@ { - "meta": - { - "title": "Remove HUD", - "author": "Dolphin Team", - "description": "Skips drawing elements designated as the HUD. Can be used for taking screenshots or increasing immersion." - }, - "features": [ - { - "group": "HUD", - "action": "skip" - } - ] -} \ No newline at end of file + "actions": [ + { + "data": {}, + "factory_name": "skip" + } + ], + "assets": [ + ], + "meta": { + "author": "Dolphin Team", + "description": "Skips drawing elements designated as the HUD. Can be used for taking screenshots or increasing immersion.", + "mod_version": "1.0.0", + "schema_version": 1, + "title": "Remove HUD" + }, + "tag_to_actions": { + "User Interface": [ + 0 + ] + } +} diff --git a/Data/Sys/Load/GraphicMods/All Games Native Resolution Bloom/bloom.glsl b/Data/Sys/Load/GraphicMods/All Games Native Resolution Bloom/bloom.glsl new file mode 100644 index 0000000000..53b286ac00 --- /dev/null +++ b/Data/Sys/Load/GraphicMods/All Games Native Resolution Bloom/bloom.glsl @@ -0,0 +1,66 @@ +float4 SampleTexmap(uint texmap, float3 coords) +{ + for (uint i = 0; i < 8; i++) + { + if (texmap == i) + { + return texture(samp[i], coords); + } + } + return float4(0, 0, 0, 1); +} + +float2 GetTextureSize(uint texmap) +{ + for (uint i = 0; i < 8; i++) + { + if (texmap == i) + { + return float2(textureSize(samp[i], 0)); + } + } + return float2(0, 0); +} + +vec4 custom_main( in CustomShaderData data ) +{ + if (data.texcoord_count == 0) + { + return data.final_color; + } + + if (data.tev_stage_count == 0) + { + return data.final_color; + } + + uint efb = data.tev_stages[0].texmap; + float3 coords = data.texcoord[0]; + float4 out_color = SampleTexmap(efb, coords); + float2 size = GetTextureSize(efb); + + // If options are added to the UI, include custom radius and intensity, radius should be around IR - 1. + // Small values decrease bloom area, but can lead to broken bloom if too small. + float intensity = 1.0; + + float radius = 3; + float dx = 1.0/size.x; + float dy = 1.0/size.y; + float x; + float y; + float count = 1.0; + float4 color = float4(0.0, 0.0, 0.0, 0.0); + + for (x = -radius; x <= radius; x++) + { + for (y = -radius; y <= radius; y++) + { + count += 1.0; + float3 off_coords = float3(coords.x + x*dx, coords.y + y*dy, coords.z); + color += SampleTexmap(efb, off_coords); + } + } + + out_color = color / count * intensity; + return out_color; +} diff --git a/Data/Sys/Load/GraphicMods/All Games Native Resolution Bloom/bloom.material b/Data/Sys/Load/GraphicMods/All Games Native Resolution Bloom/bloom.material new file mode 100644 index 0000000000..bbde71d97e --- /dev/null +++ b/Data/Sys/Load/GraphicMods/All Games Native Resolution Bloom/bloom.material @@ -0,0 +1,4 @@ +{ + "shader_asset": "bloom_shader", + "values": [] +} \ No newline at end of file diff --git a/Data/Sys/Load/GraphicMods/All Games Native Resolution Bloom/bloom.shader b/Data/Sys/Load/GraphicMods/All Games Native Resolution Bloom/bloom.shader new file mode 100644 index 0000000000..12c82c5995 --- /dev/null +++ b/Data/Sys/Load/GraphicMods/All Games Native Resolution Bloom/bloom.shader @@ -0,0 +1,3 @@ +{ + "properties": [] +} \ No newline at end of file diff --git a/Data/Sys/Load/GraphicMods/All Games Native Resolution Bloom/metadata.json b/Data/Sys/Load/GraphicMods/All Games Native Resolution Bloom/metadata.json index 0616392606..34232959bc 100644 --- a/Data/Sys/Load/GraphicMods/All Games Native Resolution Bloom/metadata.json +++ b/Data/Sys/Load/GraphicMods/All Games Native Resolution Bloom/metadata.json @@ -1,20 +1,42 @@ { - "meta": - { - "title": "Native Resolution Bloom", - "author": "Dolphin Team", - "description": "Scales bloom effects to draw at their native resolution, regardless of internal resolution. Results in bloom looking much more natural at higher resolutions but may cause shimmering." - }, - "features": - [ - { - "group": "Bloom", - "action": "scale", - "action_data": { - "X": 1.0, - "Y": 1.0, - "Z": 1.0 - } - } - ] -} \ No newline at end of file + "actions": [ + { + "data": { + "active": true, + "passes": [ + { + "pixel_material_asset": "bloom_material" + } + ] + }, + "factory_name": "custom_pipeline" + } + ], + "assets": [ + { + "data": { + "metadata": "bloom.shader", + "shader": "bloom.glsl" + }, + "id": "bloom_shader" + }, + { + "data": { + "metadata": "bloom.material" + }, + "id": "bloom_material" + } + ], + "meta": { + "author": "Dolphin Team", + "description": "Scales bloom effects to draw at their native resolution, regardless of internal resolution. Results in bloom looking much more natural at higher resolutions.", + "mod_version": "1.0.0", + "schema_version": 1, + "title": "Native Resolution Bloom" + }, + "tag_to_actions": { + "Bloom": [ + 0 + ] + } +} diff --git a/Data/Sys/Load/GraphicMods/All Games Native Resolution DOF/dof.glsl b/Data/Sys/Load/GraphicMods/All Games Native Resolution DOF/dof.glsl new file mode 100644 index 0000000000..53b286ac00 --- /dev/null +++ b/Data/Sys/Load/GraphicMods/All Games Native Resolution DOF/dof.glsl @@ -0,0 +1,66 @@ +float4 SampleTexmap(uint texmap, float3 coords) +{ + for (uint i = 0; i < 8; i++) + { + if (texmap == i) + { + return texture(samp[i], coords); + } + } + return float4(0, 0, 0, 1); +} + +float2 GetTextureSize(uint texmap) +{ + for (uint i = 0; i < 8; i++) + { + if (texmap == i) + { + return float2(textureSize(samp[i], 0)); + } + } + return float2(0, 0); +} + +vec4 custom_main( in CustomShaderData data ) +{ + if (data.texcoord_count == 0) + { + return data.final_color; + } + + if (data.tev_stage_count == 0) + { + return data.final_color; + } + + uint efb = data.tev_stages[0].texmap; + float3 coords = data.texcoord[0]; + float4 out_color = SampleTexmap(efb, coords); + float2 size = GetTextureSize(efb); + + // If options are added to the UI, include custom radius and intensity, radius should be around IR - 1. + // Small values decrease bloom area, but can lead to broken bloom if too small. + float intensity = 1.0; + + float radius = 3; + float dx = 1.0/size.x; + float dy = 1.0/size.y; + float x; + float y; + float count = 1.0; + float4 color = float4(0.0, 0.0, 0.0, 0.0); + + for (x = -radius; x <= radius; x++) + { + for (y = -radius; y <= radius; y++) + { + count += 1.0; + float3 off_coords = float3(coords.x + x*dx, coords.y + y*dy, coords.z); + color += SampleTexmap(efb, off_coords); + } + } + + out_color = color / count * intensity; + return out_color; +} diff --git a/Data/Sys/Load/GraphicMods/All Games Native Resolution DOF/dof.material b/Data/Sys/Load/GraphicMods/All Games Native Resolution DOF/dof.material new file mode 100644 index 0000000000..bdd31a002b --- /dev/null +++ b/Data/Sys/Load/GraphicMods/All Games Native Resolution DOF/dof.material @@ -0,0 +1,4 @@ +{ + "shader_asset": "dof_shader", + "values": [] +} \ No newline at end of file diff --git a/Data/Sys/Load/GraphicMods/All Games Native Resolution DOF/dof.shader b/Data/Sys/Load/GraphicMods/All Games Native Resolution DOF/dof.shader new file mode 100644 index 0000000000..12c82c5995 --- /dev/null +++ b/Data/Sys/Load/GraphicMods/All Games Native Resolution DOF/dof.shader @@ -0,0 +1,3 @@ +{ + "properties": [] +} \ No newline at end of file diff --git a/Data/Sys/Load/GraphicMods/All Games Native Resolution DOF/metadata.json b/Data/Sys/Load/GraphicMods/All Games Native Resolution DOF/metadata.json index 4aeea2e7c0..e8af58db2c 100644 --- a/Data/Sys/Load/GraphicMods/All Games Native Resolution DOF/metadata.json +++ b/Data/Sys/Load/GraphicMods/All Games Native Resolution DOF/metadata.json @@ -1,20 +1,42 @@ { - "meta": - { - "title": "Native Resolution DOF", - "author": "Dolphin Team", - "description": "Scales DOF effects to draw at their native resolution, regardless of internal resolution. Results in DOF looking much more natural at higher resolutions but may cause shimmering." - }, - "features": - [ - { - "group": "DOF", - "action": "scale", - "action_data": { - "X": 1.0, - "Y": 1.0, - "Z": 1.0 - } - } - ] + "actions": [ + { + "data": { + "active": true, + "passes": [ + { + "pixel_material_asset": "dof_material" + } + ] + }, + "factory_name": "custom_pipeline" + } + ], + "assets": [ + { + "data": { + "metadata": "dof.shader", + "shader": "dof.glsl" + }, + "id": "dof_shader" + }, + { + "data": { + "metadata": "dof.material" + }, + "id": "dof_material" + } + ], + "meta": { + "author": "Dolphin Team", + "description": "Scales depth of field (dof) effects to draw at their native resolution, regardless of internal resolution. Results for dof looking much more natural at higher resolutions.", + "mod_version": "1.0.0", + "schema_version": 1, + "title": "Native Resolution DOF" + }, + "tag_to_actions": { + "Depth of Field": [ + 0 + ] + } } diff --git a/Data/Sys/Load/GraphicMods/Arc Rise Fantasia/metadata.json b/Data/Sys/Load/GraphicMods/Arc Rise Fantasia/metadata.json index d1a072f611..620377adc0 100644 --- a/Data/Sys/Load/GraphicMods/Arc Rise Fantasia/metadata.json +++ b/Data/Sys/Load/GraphicMods/Arc Rise Fantasia/metadata.json @@ -1,19 +1,29 @@ { - "meta": - { - "title": "Bloom Texture Definitions", - "author": "iwubcode" - }, - "groups": - [ - { - "name": "Bloom", - "targets": [ - { - "type": "efb", - "texture_filename": "efb1_n33_160x112_6" - } - ] - } - ] -} \ No newline at end of file + "actions": [], + "assets": [], + "default_hash_policy": { + "attributes": "" + }, + "meta": { + "author": "iwubcode", + "description": "", + "mod_version": "", + "schema_version": 1, + "title": "Arc Rise Fantasia Definitions" + }, + "tag_to_actions": {}, + "tags": [], + "target_to_actions": { + "0": [] + }, + "targets": [ + { + "id": "16030373046293997871", + "name": "", + "tags": [ + "Bloom" + ], + "type": "int" + } + ] +} diff --git a/Data/Sys/Load/GraphicMods/Donkey Kong Country Returns/metadata.json b/Data/Sys/Load/GraphicMods/Donkey Kong Country Returns/metadata.json index d07afa792d..85cb7f5139 100644 --- a/Data/Sys/Load/GraphicMods/Donkey Kong Country Returns/metadata.json +++ b/Data/Sys/Load/GraphicMods/Donkey Kong Country Returns/metadata.json @@ -1,19 +1,27 @@ { - "meta": - { - "title": "Bloom Texture Definitions", - "author": "iwubcode" - }, - "groups": - [ - { - "name": "Bloom", - "targets": [ - { - "type": "efb", - "texture_filename": "efb1_n2_320x224_4" - } - ] - } - ] -} \ No newline at end of file + "actions": [], + "assets": [], + "default_hash_policy": { + "attributes": "" + }, + "meta": { + "author": "iwubcode", + "description": "", + "mod_version": "", + "schema_version": 1, + "title": "Donkey Kong Country Returns Definitions" + }, + "tag_to_actions": {}, + "tags": [], + "target_to_actions": {}, + "targets": [ + { + "id": "3405476862620419263", + "name": "", + "tags": [ + "Bloom" + ], + "type": "int" + } + ] +} diff --git a/Data/Sys/Load/GraphicMods/Monster Hunter Tri/metadata.json b/Data/Sys/Load/GraphicMods/Monster Hunter Tri/metadata.json index 2401f8cf65..b5a5993ebe 100644 --- a/Data/Sys/Load/GraphicMods/Monster Hunter Tri/metadata.json +++ b/Data/Sys/Load/GraphicMods/Monster Hunter Tri/metadata.json @@ -1,27 +1,27 @@ { - "meta": - { - "title": "Bloom Texture Definitions", - "author": "iwubcode" - }, - "groups": - [ - { - "name": "Bloom", - "targets": [ - { - "type": "efb", - "texture_filename": "efb1_n3_80x56_6" - }, - { - "type": "efb", - "texture_filename": "efb1_n2_160x112_6" - }, - { - "type": "efb", - "texture_filename": "efb1_n6_320x224_6" - } - ] - } - ] -} \ No newline at end of file + "actions": [], + "assets": [], + "default_hash_policy": { + "attributes": "" + }, + "meta": { + "author": "iwubcode", + "description": "", + "mod_version": "", + "schema_version": 1, + "title": "Monster Hunter Tri Definitions" + }, + "tag_to_actions": {}, + "tags": [], + "target_to_actions": {}, + "targets": [ + { + "id": "13233451943079225832", + "name": "", + "tags": [ + "Bloom" + ], + "type": "int" + } + ] +} diff --git a/Data/Sys/Load/GraphicMods/Nights Journey of Dreams/metadata.json b/Data/Sys/Load/GraphicMods/Nights Journey of Dreams/metadata.json index f234ad0dba..8c1e00b5fd 100644 --- a/Data/Sys/Load/GraphicMods/Nights Journey of Dreams/metadata.json +++ b/Data/Sys/Load/GraphicMods/Nights Journey of Dreams/metadata.json @@ -1,19 +1,27 @@ { - "meta": - { - "title": "Bloom Texture Definitions", - "author": "iwubcode" - }, - "groups": - [ - { - "name": "Bloom", - "targets": [ - { - "type": "efb", - "texture_filename": "efb1_n000019_128x128_4" - } - ] - } - ] -} \ No newline at end of file + "actions": [], + "assets": [], + "default_hash_policy": { + "attributes": "" + }, + "meta": { + "author": "", + "description": "", + "mod_version": "", + "schema_version": 1, + "title": "Nights Journey of Dreams Definitions" + }, + "tag_to_actions": {}, + "tags": [], + "target_to_actions": {}, + "targets": [ + { + "id": "13920250048690583912", + "name": "", + "tags": [ + "Bloom" + ], + "type": "int" + } + ] +} diff --git a/Data/Sys/Load/GraphicMods/Okami/metadata.json b/Data/Sys/Load/GraphicMods/Okami/metadata.json index 3c93e5f88f..18a7b39bfc 100644 --- a/Data/Sys/Load/GraphicMods/Okami/metadata.json +++ b/Data/Sys/Load/GraphicMods/Okami/metadata.json @@ -1,19 +1,27 @@ { - "meta": - { - "title": "Bloom Texture Definitions", - "author": "iwubcode" - }, - "groups": - [ - { - "name": "Bloom", - "targets": [ - { - "type": "efb", - "texture_filename": "efb1_n51_320x240_6" - } - ] - } - ] -} \ No newline at end of file + "actions": [], + "assets": [], + "default_hash_policy": { + "attributes": "" + }, + "meta": { + "author": "iwubcode", + "description": "", + "mod_version": "", + "schema_version": 1, + "title": "Okami Definitions" + }, + "tag_to_actions": {}, + "tags": [], + "target_to_actions": {}, + "targets": [ + { + "id": "6230608514450344714", + "name": "", + "tags": [ + "Bloom" + ], + "type": "int" + } + ] +} diff --git a/Data/Sys/Load/GraphicMods/Pandora's Tower/metadata.json b/Data/Sys/Load/GraphicMods/Pandora's Tower/metadata.json index dcbdadd49a..898fbe5647 100644 --- a/Data/Sys/Load/GraphicMods/Pandora's Tower/metadata.json +++ b/Data/Sys/Load/GraphicMods/Pandora's Tower/metadata.json @@ -1,36 +1,27 @@ { - "meta": - { - "title": "Bloom and DOF Texture Definitions", - "author": "linckandrea" - }, - "groups": - [ - { - "name": "Bloom", - "targets": [ - { - "type": "efb", - "texture_filename": "efb1_n09_20x15_1" - }, - { - "type": "efb", - "texture_filename": "efb1_n21_20x15_1" - } - ] - }, - { - "name": "DOF", - "targets": [ - { - "type": "efb", - "texture_filename": "efb1_n10_320x240_4" - }, - { - "type": "efb", - "texture_filename": "efb1_n11_320x240_1" - } - ] - } - ] + "actions": [], + "assets": [], + "default_hash_policy": { + "attributes": "" + }, + "meta": { + "author": "iwubcode", + "description": "", + "mod_version": "", + "schema_version": 1, + "title": "Pandoras Tower Definitions" + }, + "tag_to_actions": {}, + "tags": [], + "target_to_actions": {}, + "targets": [ + { + "id": "10512703869395082884", + "name": "", + "tags": [ + "Bloom" + ], + "type": "int" + } + ] } diff --git a/Data/Sys/Load/GraphicMods/The Conduit/metadata.json b/Data/Sys/Load/GraphicMods/The Conduit/metadata.json index ad9307d041..2840942937 100644 --- a/Data/Sys/Load/GraphicMods/The Conduit/metadata.json +++ b/Data/Sys/Load/GraphicMods/The Conduit/metadata.json @@ -1,31 +1,47 @@ { - "meta": - { - "title": "Bloom Texture Definitions", - "author": "iwubcode" - }, - "groups": - [ - { - "name": "Bloom", - "targets": [ - { - "type": "efb", - "texture_filename": "efb1_n000022_40x28_6" - }, - { - "type": "efb", - "texture_filename": "efb1_n000021_80x56_6" - }, - { - "type": "efb", - "texture_filename": "efb1_n000020_160x112_6" - }, - { - "type": "efb", - "texture_filename": "efb1_n000025_320x224_6" - } - ] - } - ] -} \ No newline at end of file + "actions": [], + "assets": [], + "default_hash_policy": { + "attributes": "" + }, + "meta": { + "author": "iwubcode", + "description": "", + "mod_version": "", + "schema_version": 1, + "title": "The Conduit Definitions" + }, + "tag_to_actions": {}, + "tags": [], + "target_to_actions": { + "0": [], + "1": [], + "2": [] + }, + "targets": [ + { + "id": "1759966615601106229", + "name": "", + "tags": [ + "Bloom" + ], + "type": "int" + }, + { + "id": "2423050791446092715", + "name": "", + "tags": [ + "Bloom" + ], + "type": "int" + }, + { + "id": "4157223477088026000", + "name": "", + "tags": [ + "Bloom" + ], + "type": "int" + } + ] +} diff --git a/Data/Sys/Load/GraphicMods/The Legend of Zelda Twilight Princess/metadata.json b/Data/Sys/Load/GraphicMods/The Legend of Zelda Twilight Princess/metadata.json index 2ec7bce760..c569404368 100644 --- a/Data/Sys/Load/GraphicMods/The Legend of Zelda Twilight Princess/metadata.json +++ b/Data/Sys/Load/GraphicMods/The Legend of Zelda Twilight Princess/metadata.json @@ -1,23 +1,27 @@ { - "meta": - { - "title": "Bloom Texture Definitions", - "author": "iwubcode" - }, - "groups": - [ - { - "name": "Bloom", - "targets": [ - { - "type": "efb", - "texture_filename": "efb1_n55_80x57_6" - }, - { - "type": "efb", - "texture_filename": "efb1_n54_160x114_6" - } - ] - } - ] -} \ No newline at end of file + "actions": [], + "assets": [], + "default_hash_policy": { + "attributes": "" + }, + "meta": { + "author": "iwubcode", + "description": "", + "mod_version": "", + "schema_version": 1, + "title": "Twilight Princess Definitions" + }, + "tag_to_actions": {}, + "tags": [], + "target_to_actions": {}, + "targets": [ + { + "id": "10063434684210657575", + "name": "", + "tags": [ + "Bloom" + ], + "type": "int" + } + ] +} diff --git a/Data/Sys/Load/GraphicMods/Wii Play/metadata.json b/Data/Sys/Load/GraphicMods/Wii Play/metadata.json index f0015dcec1..994b14879d 100644 --- a/Data/Sys/Load/GraphicMods/Wii Play/metadata.json +++ b/Data/Sys/Load/GraphicMods/Wii Play/metadata.json @@ -1,27 +1,43 @@ { - "meta": - { - "title": "Bloom Texture Definitions", - "author": "iwubcode" - }, - "groups": - [ - { - "name": "Bloom", - "targets": [ - { - "type": "efb", - "texture_filename": "efb1_n9_80x58_6" - }, - { - "type": "efb", - "texture_filename": "efb1_n21_80x57_6" - }, - { - "type": "efb", - "texture_filename": "efb1_n2_320x228_6" - } - ] - } - ] -} \ No newline at end of file + "actions": [], + "assets": [], + "default_hash_policy": { + "attributes": "" + }, + "meta": { + "author": "iwubcode", + "description": "", + "mod_version": "", + "schema_version": 1, + "title": "Wii Play Definitions" + }, + "tag_to_actions": {}, + "tags": [], + "target_to_actions": {}, + "targets": [ + { + "id": "5238528733911143545", + "name": "", + "tags": [ + "Bloom" + ], + "type": "int" + }, + { + "id": "10820371786676733846", + "name": "", + "tags": [ + "Bloom" + ], + "type": "int" + }, + { + "id": "18418729168554791402", + "name": "", + "tags": [ + "Bloom" + ], + "type": "int" + } + ] +} diff --git a/Data/Sys/Load/GraphicMods/Xenoblade Chronicles/metadata.json b/Data/Sys/Load/GraphicMods/Xenoblade Chronicles/metadata.json index 9dc56f815c..293c94e703 100644 --- a/Data/Sys/Load/GraphicMods/Xenoblade Chronicles/metadata.json +++ b/Data/Sys/Load/GraphicMods/Xenoblade Chronicles/metadata.json @@ -1,31 +1,67 @@ { - "meta": - { - "title": "Bloom Texture Definitions", - "author": "iwubcode" - }, - "groups": - [ - { - "name": "Bloom", - "targets": [ - { - "type": "efb", - "texture_filename": "efb1_n15_20x16_4" - }, - { - "type": "efb", - "texture_filename": "efb1_n9_40x30_4" - }, - { - "type": "efb", - "texture_filename": "efb1_n7_80x58_4" - }, - { - "type": "efb", - "texture_filename": "efb1_n1_320x228_4" - } - ] - } - ] -} \ No newline at end of file + "actions": [], + "assets": [], + "default_hash_policy": { + "attributes": "" + }, + "meta": { + "author": "iwubcode", + "description": "", + "mod_version": "", + "schema_version": 1, + "title": "Xenoblade Definitions" + }, + "tag_to_actions": {}, + "tags": [], + "target_to_actions": {}, + "targets": [ + { + "id": "1873769556004204196", + "name": "", + "tags": [ + "Bloom" + ], + "type": "int" + }, + { + "id": "2784022002606692122", + "name": "", + "tags": [ + "Bloom" + ], + "type": "int" + }, + { + "id": "5940037079112913957", + "name": "", + "tags": [ + "Bloom" + ], + "type": "int" + }, + { + "id": "8606219869014623335", + "name": "", + "tags": [ + "Bloom" + ], + "type": "int" + }, + { + "id": "11236664535429889509", + "name": "", + "tags": [ + "Bloom" + ], + "type": "int" + }, + { + "id": "15824374617827814652", + "name": "", + "tags": [ + "Bloom" + ], + "type": "int" + } + ] +} diff --git a/Externals/imgui/CMakeLists.txt b/Externals/imgui/CMakeLists.txt index 43274cf88a..f89dba33ff 100644 --- a/Externals/imgui/CMakeLists.txt +++ b/Externals/imgui/CMakeLists.txt @@ -10,6 +10,7 @@ set(SRCS imgui_draw.cpp imgui_tables.cpp imgui_widgets.cpp + misc/cpp/imgui_stdlib.cpp ) add_library(imgui STATIC ${SRCS}) diff --git a/Externals/imgui/imconfig.h b/Externals/imgui/imconfig.h index e4771ba50c..8f8bc3b9a9 100644 --- a/Externals/imgui/imconfig.h +++ b/Externals/imgui/imconfig.h @@ -33,7 +33,7 @@ //---- Disable all of Dear ImGui or don't implement standard windows/tools. // It is very strongly recommended to NOT disable the demo windows and debug tool during development. They are extremely useful in day to day work. Please read comments in imgui_demo.cpp. //#define IMGUI_DISABLE // Disable everything: all headers and source files will be empty. -#define IMGUI_DISABLE_DEMO_WINDOWS // Disable demo windows: ShowDemoWindow()/ShowStyleEditor() will be empty. +//#define IMGUI_DISABLE_DEMO_WINDOWS // Disable demo windows: ShowDemoWindow()/ShowStyleEditor() will be empty. //#define IMGUI_DISABLE_DEBUG_TOOLS // Disable metrics/debugger and other debug tools: ShowMetricsWindow(), ShowDebugLogWindow() and ShowIDStackToolWindow() will be empty. //---- Don't implement some functions to reduce linkage requirements. diff --git a/Externals/watcher/CMakeLists.txt b/Externals/watcher/CMakeLists.txt new file mode 100644 index 0000000000..097f16d647 --- /dev/null +++ b/Externals/watcher/CMakeLists.txt @@ -0,0 +1,4 @@ +add_library(watcher INTERFACE IMPORTED GLOBAL) +set_target_properties(watcher PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_LIST_DIR}/watcher/include +) diff --git a/Externals/watcher/watcher b/Externals/watcher/watcher new file mode 160000 index 0000000000..0d6b9b409c --- /dev/null +++ b/Externals/watcher/watcher @@ -0,0 +1 @@ +Subproject commit 0d6b9b409ccaed6313437ea3dc8b2fc078f3d25b diff --git a/Source/Android/jni/Cheats/GraphicsMod.cpp b/Source/Android/jni/Cheats/GraphicsMod.cpp index 627351f546..be8f916e72 100644 --- a/Source/Android/jni/Cheats/GraphicsMod.cpp +++ b/Source/Android/jni/Cheats/GraphicsMod.cpp @@ -5,13 +5,14 @@ #include -#include "VideoCommon/GraphicsModSystem/Config/GraphicsMod.h" +#include "VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.h" #include "jni/AndroidCommon/AndroidCommon.h" #include "jni/AndroidCommon/IDCache.h" -static GraphicsModConfig* GetPointer(JNIEnv* env, jobject obj) +static GraphicsModSystem::Config::GraphicsModGroup::GraphicsModWithMetadata* GetPointer(JNIEnv* env, + jobject obj) { - return reinterpret_cast( + return reinterpret_cast( env->GetLongField(obj, IDCache::GetGraphicsModPointer())); } @@ -20,20 +21,20 @@ extern "C" { JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_features_cheats_model_GraphicsMod_getName(JNIEnv* env, jobject obj) { - return ToJString(env, GetPointer(env, obj)->m_title); + return ToJString(env, GetPointer(env, obj)->m_mod.m_title); } JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_features_cheats_model_GraphicsMod_getCreator(JNIEnv* env, jobject obj) { - return ToJString(env, GetPointer(env, obj)->m_author); + return ToJString(env, GetPointer(env, obj)->m_mod.m_author); } JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_features_cheats_model_GraphicsMod_getNotes(JNIEnv* env, jobject obj) { - return ToJString(env, GetPointer(env, obj)->m_description); + return ToJString(env, GetPointer(env, obj)->m_mod.m_description); } JNIEXPORT jboolean JNICALL diff --git a/Source/Android/jni/Cheats/GraphicsModGroup.cpp b/Source/Android/jni/Cheats/GraphicsModGroup.cpp index 7affca0ef6..fdeb36fed0 100644 --- a/Source/Android/jni/Cheats/GraphicsModGroup.cpp +++ b/Source/Android/jni/Cheats/GraphicsModGroup.cpp @@ -3,21 +3,21 @@ #include -#include #include -#include "VideoCommon/GraphicsModSystem/Config/GraphicsMod.h" #include "VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.h" #include "jni/AndroidCommon/AndroidCommon.h" #include "jni/AndroidCommon/IDCache.h" -static GraphicsModGroupConfig* GetPointer(JNIEnv* env, jobject obj) +static GraphicsModSystem::Config::GraphicsModGroup* GetPointer(JNIEnv* env, jobject obj) { - return reinterpret_cast( + return reinterpret_cast( env->GetLongField(obj, IDCache::GetGraphicsModGroupPointer())); } -jobject GraphicsModToJava(JNIEnv* env, GraphicsModConfig* mod, jobject jGraphicsModGroup) +jobject GraphicsModToJava(JNIEnv* env, + GraphicsModSystem::Config::GraphicsModGroup::GraphicsModWithMetadata* mod, + jobject jGraphicsModGroup) { return env->NewObject(IDCache::GetGraphicsModClass(), IDCache::GetGraphicsModConstructor(), reinterpret_cast(mod), jGraphicsModGroup); @@ -36,34 +36,23 @@ JNIEXPORT jobjectArray JNICALL Java_org_dolphinemu_dolphinemu_features_cheats_model_GraphicsModGroup_getMods(JNIEnv* env, jobject obj) { - GraphicsModGroupConfig* mod_group = GetPointer(env, obj); + GraphicsModSystem::Config::GraphicsModGroup* mod_group = GetPointer(env, obj); + std::vector mods; - std::set groups; - - for (const GraphicsModConfig& mod : mod_group->GetMods()) + for (auto& mod : mod_group->GetMods()) { - for (const GraphicsTargetGroupConfig& group : mod.m_groups) - groups.insert(group.m_name); - } - - std::vector mods; - - for (GraphicsModConfig& mod : mod_group->GetMods()) - { - // If no group matches the mod's features, or if the mod has no features, skip it - if (std::ranges::none_of(mod.m_features, [&groups](const GraphicsModFeatureConfig& feature) { - return groups.contains(feature.m_group); - })) - { + if (mod.m_mod.m_actions.empty()) continue; - } mods.push_back(&mod); } return VectorToJObjectArray( env, mods, IDCache::GetGraphicsModClass(), - [obj](JNIEnv* env, GraphicsModConfig* mod) { return GraphicsModToJava(env, mod, obj); }); + [obj](JNIEnv* env, + GraphicsModSystem::Config::GraphicsModGroup::GraphicsModWithMetadata* mod) { + return GraphicsModToJava(env, mod, obj); + }); } JNIEXPORT void JNICALL @@ -76,7 +65,7 @@ JNIEXPORT jobject JNICALL Java_org_dolphinemu_dolphinemu_features_cheats_model_GraphicsModGroup_load(JNIEnv* env, jclass, jstring jGameId) { - auto* mod_group = new GraphicsModGroupConfig(GetJString(env, jGameId)); + auto* mod_group = new GraphicsModSystem::Config::GraphicsModGroup(GetJString(env, jGameId)); mod_group->Load(); diff --git a/Source/Core/Common/CMakeLists.txt b/Source/Core/Common/CMakeLists.txt index 8f3eeb0a27..d915ea75bd 100644 --- a/Source/Core/Common/CMakeLists.txt +++ b/Source/Core/Common/CMakeLists.txt @@ -89,6 +89,7 @@ add_library(common IOFile.h JitRegister.cpp JitRegister.h + JsonUtil.cpp JsonUtil.h JsonUtil.cpp Lazy.h diff --git a/Source/Core/Common/CommonPaths.h b/Source/Core/Common/CommonPaths.h index 0f6b7adfb6..11ce49bec8 100644 --- a/Source/Core/Common/CommonPaths.h +++ b/Source/Core/Common/CommonPaths.h @@ -61,6 +61,7 @@ #define RIIVOLUTION_DIR "Riivolution" #define DUMP_DIR "Dump" #define DUMP_TEXTURES_DIR "Textures" +#define DUMP_MESHES_DIR "Meshes" #define DUMP_FRAMES_DIR "Frames" #define DUMP_OBJECTS_DIR "Objects" #define DUMP_AUDIO_DIR "Audio" @@ -89,6 +90,7 @@ #define GRAPHICSMOD_DIR "GraphicMods" #define WIISDSYNC_DIR "WiiSDSync" #define ASSEMBLY_DIR "SavedAssembly" +#define GRAPHICSMODEDITOR_DIR "GraphicsModEditor" // This one is only used to remove it if it was present #define SHADERCACHE_LEGACY_DIR "ShaderCache" diff --git a/Source/Core/Common/FileUtil.cpp b/Source/Core/Common/FileUtil.cpp index ee2b330812..18f6a05b23 100644 --- a/Source/Core/Common/FileUtil.cpp +++ b/Source/Core/Common/FileUtil.cpp @@ -869,6 +869,7 @@ static void RebuildUserDirectories(unsigned int dir_index) s_user_paths[D_DUMPOBJECTS_IDX] = s_user_paths[D_DUMP_IDX] + DUMP_OBJECTS_DIR DIR_SEP; s_user_paths[D_DUMPAUDIO_IDX] = s_user_paths[D_DUMP_IDX] + DUMP_AUDIO_DIR DIR_SEP; s_user_paths[D_DUMPTEXTURES_IDX] = s_user_paths[D_DUMP_IDX] + DUMP_TEXTURES_DIR DIR_SEP; + s_user_paths[D_DUMPMESHES_IDX] = s_user_paths[D_DUMP_IDX] + DUMP_MESHES_DIR DIR_SEP; s_user_paths[D_DUMPDSP_IDX] = s_user_paths[D_DUMP_IDX] + DUMP_DSP_DIR DIR_SEP; s_user_paths[D_DUMPSSL_IDX] = s_user_paths[D_DUMP_IDX] + DUMP_SSL_DIR DIR_SEP; s_user_paths[D_DUMPDEBUG_IDX] = s_user_paths[D_DUMP_IDX] + DUMP_DEBUG_DIR DIR_SEP; @@ -954,6 +955,7 @@ static void RebuildUserDirectories(unsigned int dir_index) s_user_paths[D_DUMPOBJECTS_IDX] = s_user_paths[D_DUMP_IDX] + DUMP_OBJECTS_DIR DIR_SEP; s_user_paths[D_DUMPAUDIO_IDX] = s_user_paths[D_DUMP_IDX] + DUMP_AUDIO_DIR DIR_SEP; s_user_paths[D_DUMPTEXTURES_IDX] = s_user_paths[D_DUMP_IDX] + DUMP_TEXTURES_DIR DIR_SEP; + s_user_paths[D_DUMPMESHES_IDX] = s_user_paths[D_DUMP_IDX] + DUMP_MESHES_DIR DIR_SEP; s_user_paths[D_DUMPDSP_IDX] = s_user_paths[D_DUMP_IDX] + DUMP_DSP_DIR DIR_SEP; s_user_paths[D_DUMPSSL_IDX] = s_user_paths[D_DUMP_IDX] + DUMP_SSL_DIR DIR_SEP; s_user_paths[D_DUMPDEBUG_IDX] = s_user_paths[D_DUMP_IDX] + DUMP_DEBUG_DIR DIR_SEP; diff --git a/Source/Core/Common/FileUtil.h b/Source/Core/Common/FileUtil.h index a887ffd8f3..248562c231 100644 --- a/Source/Core/Common/FileUtil.h +++ b/Source/Core/Common/FileUtil.h @@ -51,6 +51,7 @@ enum D_DUMPOBJECTS_IDX, D_DUMPAUDIO_IDX, D_DUMPTEXTURES_IDX, + D_DUMPMESHES_IDX, D_DUMPDSP_IDX, D_DUMPSSL_IDX, D_DUMPDEBUG_IDX, diff --git a/Source/Core/Core/System.cpp b/Source/Core/Core/System.cpp index 695d5861fc..2c4c1472af 100644 --- a/Source/Core/Core/System.cpp +++ b/Source/Core/Core/System.cpp @@ -36,6 +36,9 @@ #include "VideoCommon/CommandProcessor.h" #include "VideoCommon/Fifo.h" #include "VideoCommon/GeometryShaderManager.h" +#include "VideoCommon/GraphicsModEditor/EditorMain.h" +#include "VideoCommon/GraphicsModSystem/Runtime/CustomResourceManager.h" +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h" #include "VideoCommon/PixelEngine.h" #include "VideoCommon/PixelShaderManager.h" #include "VideoCommon/VertexShaderManager.h" @@ -95,9 +98,12 @@ struct System::Impl Interpreter m_interpreter; JitInterface m_jit_interface; VideoCommon::CustomAssetLoader m_custom_asset_loader; + VideoCommon::CustomResourceManager m_custom_resource_manager; FifoPlayer m_fifo_player; FifoRecorder m_fifo_recorder; Movie::MovieManager m_movie; + GraphicsModEditor::EditorMain m_graphics_mod_editor; + GraphicsModSystem::Runtime::GraphicsModManager m_graphics_mod_manager; }; System::System() : m_impl{std::make_unique(*this)} @@ -332,4 +338,18 @@ VideoCommon::CustomAssetLoader& System::GetCustomAssetLoader() const { return m_impl->m_custom_asset_loader; } + +VideoCommon::CustomResourceManager& System::GetCustomResourceManager() const +{ + return m_impl->m_custom_resource_manager; +} + +GraphicsModEditor::EditorMain& System::GetGraphicsModEditor() const +{ + return m_impl->m_graphics_mod_editor; +} +GraphicsModSystem::Runtime::GraphicsModManager& System::GetGraphicsModManager() const +{ + return m_impl->m_graphics_mod_manager; +} } // namespace Core diff --git a/Source/Core/Core/System.h b/Source/Core/Core/System.h index 9ec8391ff0..a481f09f57 100644 --- a/Source/Core/Core/System.h +++ b/Source/Core/Core/System.h @@ -53,6 +53,14 @@ namespace GPFifo { class GPFifoManager; } +namespace GraphicsModEditor +{ +class EditorMain; +} +namespace GraphicsModSystem::Runtime +{ +class GraphicsModManager; +} namespace IOS::HLE { class EmulationKernel; @@ -108,7 +116,8 @@ class SystemTimersManager; namespace VideoCommon { class CustomAssetLoader; -} +class CustomResourceManager; +} // namespace VideoCommon namespace VideoInterface { class VideoInterfaceManager; @@ -194,6 +203,9 @@ public: XFStateManager& GetXFStateManager() const; VideoInterface::VideoInterfaceManager& GetVideoInterface() const; VideoCommon::CustomAssetLoader& GetCustomAssetLoader() const; + VideoCommon::CustomResourceManager& GetCustomResourceManager() const; + GraphicsModEditor::EditorMain& GetGraphicsModEditor() const; + GraphicsModSystem::Runtime::GraphicsModManager& GetGraphicsModManager() const; private: System(); diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index c8b3b0ed4d..541bd71613 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -658,12 +658,18 @@ + + + + + + @@ -687,26 +693,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + - + + + - - + + + + + + + + + + @@ -1304,12 +1349,17 @@ + + + + + @@ -1329,22 +1379,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + - + + - + + + - - + + + + + + + + + diff --git a/Source/Core/DolphinQt/Config/GraphicsModListWidget.cpp b/Source/Core/DolphinQt/Config/GraphicsModListWidget.cpp index 5bd1166d32..b1703d674a 100644 --- a/Source/Core/DolphinQt/Config/GraphicsModListWidget.cpp +++ b/Source/Core/DolphinQt/Config/GraphicsModListWidget.cpp @@ -23,7 +23,6 @@ #include "DolphinQt/QtUtils/ClearLayoutRecursively.h" #include "DolphinQt/Settings.h" #include "UICommon/GameFile.h" -#include "VideoCommon/GraphicsModSystem/Config/GraphicsMod.h" #include "VideoCommon/VideoConfig.h" GraphicsModListWidget::GraphicsModListWidget(const UICommon::GameFile& game) @@ -38,7 +37,7 @@ GraphicsModListWidget::GraphicsModListWidget(const UICommon::GameFile& game) ConnectWidgets(); RefreshModList(); - OnModChanged(std::nullopt); + OnModChanged(0); } GraphicsModListWidget::~GraphicsModListWidget() @@ -124,30 +123,17 @@ void GraphicsModListWidget::RefreshModList() m_mod_list->setCurrentItem(nullptr); m_mod_list->clear(); - m_mod_group = GraphicsModGroupConfig(m_game_id); + m_mod_group = GraphicsModSystem::Config::GraphicsModGroup(m_game_id); m_mod_group.Load(); - std::set groups; - - for (const GraphicsModConfig& mod : m_mod_group.GetMods()) + for (const auto& mod : m_mod_group.GetMods()) { - for (const GraphicsTargetGroupConfig& group : mod.m_groups) - groups.insert(group.m_name); - } - - for (const GraphicsModConfig& mod : m_mod_group.GetMods()) - { - // If no group matches the mod's features, or if the mod has no features, skip it - if (std::ranges::none_of(mod.m_features, [&groups](const GraphicsModFeatureConfig& feature) { - return groups.contains(feature.m_group); - })) - { + if (mod.m_mod.m_actions.empty()) continue; - } - QListWidgetItem* item = new QListWidgetItem(QString::fromStdString(mod.m_title)); + QListWidgetItem* item = new QListWidgetItem(QString::fromStdString(mod.m_mod.m_title)); item->setFlags(item->flags() | Qt::ItemIsUserCheckable); - item->setData(Qt::UserRole, QString::fromStdString(mod.GetAbsolutePath())); + item->setData(Qt::UserRole, static_cast(mod.m_id)); item->setCheckState(mod.m_enabled ? Qt::Checked : Qt::Unchecked); m_mod_list->addItem(item); @@ -160,14 +146,13 @@ void GraphicsModListWidget::ModSelectionChanged() return; if (m_mod_list->count() == 0) return; - const auto absolute_path = m_mod_list->currentItem()->data(Qt::UserRole).toString().toStdString(); - OnModChanged(absolute_path); + OnModChanged(m_mod_list->currentItem()->data(Qt::UserRole).toULongLong()); } void GraphicsModListWidget::ModItemChanged(QListWidgetItem* item) { - const auto absolute_path = item->data(Qt::UserRole).toString(); - GraphicsModConfig* mod = m_mod_group.GetMod(absolute_path.toStdString()); + const auto id = item->data(Qt::UserRole).toULongLong(); + auto mod = m_mod_group.GetMod(id); if (!mod) return; @@ -185,39 +170,39 @@ void GraphicsModListWidget::ModItemChanged(QListWidgetItem* item) m_needs_save = true; } -void GraphicsModListWidget::OnModChanged(const std::optional& absolute_path) +void GraphicsModListWidget::OnModChanged(u64 id) { ClearLayoutRecursively(m_mod_meta_layout); adjustSize(); - if (!absolute_path) + if (id == 0) { m_selected_mod_name->setText(tr("No graphics mod selected")); m_selected_mod_name->setAlignment(Qt::AlignCenter); return; } - const GraphicsModConfig* mod = m_mod_group.GetMod(*absolute_path); + const auto mod = m_mod_group.GetMod(id); if (!mod) return; - m_selected_mod_name->setText(QString::fromStdString(mod->m_title)); + m_selected_mod_name->setText(QString::fromStdString(mod->m_mod.m_title)); m_selected_mod_name->setAlignment(Qt::AlignLeft); QFont font = m_selected_mod_name->font(); font.setWeight(QFont::Bold); m_selected_mod_name->setFont(font); - if (!mod->m_author.empty()) + if (!mod->m_mod.m_author.empty()) { - auto* author_label = new QLabel(tr("By: %1").arg(QString::fromStdString(mod->m_author))); + auto* author_label = new QLabel(tr("By: %1").arg(QString::fromStdString(mod->m_mod.m_author))); m_mod_meta_layout->addWidget(author_label); } - if (!mod->m_description.empty()) + if (!mod->m_mod.m_description.empty()) { auto* description_label = - new QLabel(tr("Description: %1").arg(QString::fromStdString(mod->m_description))); + new QLabel(tr("Description: %1").arg(QString::fromStdString(mod->m_mod.m_description))); description_label->setWordWrap(true); m_mod_meta_layout->addWidget(description_label); } @@ -227,11 +212,9 @@ void GraphicsModListWidget::SaveModList() { for (int i = 0; i < m_mod_list->count(); i++) { - const auto absolute_path = m_mod_list->model() - ->data(m_mod_list->model()->index(i, 0), Qt::UserRole) - .toString() - .toStdString(); - m_mod_group.GetMod(absolute_path)->m_weight = i; + const auto id = + m_mod_list->model()->data(m_mod_list->model()->index(i, 0), Qt::UserRole).toULongLong(); + m_mod_group.GetMod(id)->m_weight = i; } if (m_loaded_game_is_running) @@ -247,7 +230,8 @@ void GraphicsModListWidget::SaveToDisk() m_mod_group.Save(); } -const GraphicsModGroupConfig& GraphicsModListWidget::GetGraphicsModConfig() const +const GraphicsModSystem::Config::GraphicsModGroup& +GraphicsModListWidget::GetGraphicsModConfig() const { return m_mod_group; } diff --git a/Source/Core/DolphinQt/Config/GraphicsModListWidget.h b/Source/Core/DolphinQt/Config/GraphicsModListWidget.h index ff310af56c..3e28937dca 100644 --- a/Source/Core/DolphinQt/Config/GraphicsModListWidget.h +++ b/Source/Core/DolphinQt/Config/GraphicsModListWidget.h @@ -39,7 +39,7 @@ public: void SaveToDisk(); - const GraphicsModGroupConfig& GetGraphicsModConfig() const; + const GraphicsModSystem::Config::GraphicsModGroup& GetGraphicsModConfig() const; signals: void OpenGraphicsSettings(); @@ -52,7 +52,7 @@ private: void ModSelectionChanged(); void ModItemChanged(QListWidgetItem* item); - void OnModChanged(const std::optional& absolute_path); + void OnModChanged(u64 id); void SaveModList(); @@ -72,5 +72,5 @@ private: GraphicsModWarningWidget* m_warning; std::string m_game_id; - GraphicsModGroupConfig m_mod_group; + GraphicsModSystem::Config::GraphicsModGroup m_mod_group; }; diff --git a/Source/Core/DolphinQt/RenderWidget.cpp b/Source/Core/DolphinQt/RenderWidget.cpp index 4ca6ece1a9..8834bb5e61 100644 --- a/Source/Core/DolphinQt/RenderWidget.cpp +++ b/Source/Core/DolphinQt/RenderWidget.cpp @@ -574,12 +574,12 @@ void RenderWidget::PassEventToPresenter(const QEvent* event) void RenderWidget::SetPresenterKeyMap() { static constexpr DolphinKeyMap key_map = { - Qt::Key_Tab, Qt::Key_Left, Qt::Key_Right, Qt::Key_Up, Qt::Key_Down, - Qt::Key_PageUp, Qt::Key_PageDown, Qt::Key_Home, Qt::Key_End, Qt::Key_Insert, - Qt::Key_Delete, Qt::Key_Backspace, Qt::Key_Space, Qt::Key_Return, Qt::Key_Escape, + Qt::Key_Tab, Qt::Key_Left, Qt::Key_Right, Qt::Key_Up, Qt::Key_Down, + Qt::Key_PageUp, Qt::Key_PageDown, Qt::Key_Home, Qt::Key_End, Qt::Key_Insert, + Qt::Key_Delete, Qt::Key_Backspace, Qt::Key_Space, Qt::Key_Return, Qt::Key_Escape, Qt::Key_Enter, // Keypad enter - Qt::Key_A, Qt::Key_C, Qt::Key_V, Qt::Key_X, Qt::Key_Y, - Qt::Key_Z, + Qt::Key_Control, Qt::Key_Shift, Qt::Key_A, Qt::Key_C, Qt::Key_V, + Qt::Key_X, Qt::Key_Y, Qt::Key_Z, }; g_presenter->SetKeyMap(key_map); diff --git a/Source/Core/UICommon/UICommon.cpp b/Source/Core/UICommon/UICommon.cpp index e4f79a6a6a..30aa08dccc 100644 --- a/Source/Core/UICommon/UICommon.cpp +++ b/Source/Core/UICommon/UICommon.cpp @@ -77,6 +77,7 @@ static void CreateDumpPath(std::string path) File::CreateFullPath(File::GetUserPath(D_DUMPDEBUG_IDX)); File::CreateFullPath(File::GetUserPath(D_DUMPDEBUG_BRANCHWATCH_IDX)); File::CreateFullPath(File::GetUserPath(D_DUMPDEBUG_JITBLOCKS_IDX)); + File::CreateFullPath(File::GetUserPath(D_DUMPMESHES_IDX)); } static void CreateLoadPath(std::string path) diff --git a/Source/Core/VideoBackends/D3D/D3DVertexManager.cpp b/Source/Core/VideoBackends/D3D/D3DVertexManager.cpp index 5dd38c37a9..24efebf9e7 100644 --- a/Source/Core/VideoBackends/D3D/D3DVertexManager.cpp +++ b/Source/Core/VideoBackends/D3D/D3DVertexManager.cpp @@ -213,6 +213,7 @@ void VertexManager::ResetBuffer(u32 vertex_stride) m_cur_buffer_pointer = m_base_buffer_pointer; m_end_buffer_pointer = m_base_buffer_pointer + m_cpu_vertex_buffer.size(); m_index_generator.Start(m_cpu_index_buffer.data()); + m_last_reset_pointer = m_cur_buffer_pointer; } void VertexManager::CommitBuffer(u32 num_vertices, u32 vertex_stride, u32 num_indices, diff --git a/Source/Core/VideoBackends/D3D12/D3D12VertexManager.cpp b/Source/Core/VideoBackends/D3D12/D3D12VertexManager.cpp index c0fe195e75..b09ea23833 100644 --- a/Source/Core/VideoBackends/D3D12/D3D12VertexManager.cpp +++ b/Source/Core/VideoBackends/D3D12/D3D12VertexManager.cpp @@ -111,6 +111,7 @@ void VertexManager::ResetBuffer(u32 vertex_stride) m_end_buffer_pointer = m_vertex_stream_buffer.GetCurrentHostPointer() + MAXVBUFFERSIZE; m_cur_buffer_pointer = m_vertex_stream_buffer.GetCurrentHostPointer(); m_index_generator.Start(reinterpret_cast(m_index_stream_buffer.GetCurrentHostPointer())); + m_last_reset_pointer = m_cur_buffer_pointer; } void VertexManager::CommitBuffer(u32 num_vertices, u32 vertex_stride, u32 num_indices, diff --git a/Source/Core/VideoBackends/Metal/MTLVertexManager.mm b/Source/Core/VideoBackends/Metal/MTLVertexManager.mm index 91f1d42eec..66308c3614 100644 --- a/Source/Core/VideoBackends/Metal/MTLVertexManager.mm +++ b/Source/Core/VideoBackends/Metal/MTLVertexManager.mm @@ -67,6 +67,7 @@ void Metal::VertexManager::ResetBuffer(u32 vertex_stride) m_vertex_offset = m_base_vertex * vertex_stride - vertex.second; m_cur_buffer_pointer = m_base_buffer_pointer = static_cast(vertex.first) + m_vertex_offset; m_end_buffer_pointer = m_base_buffer_pointer + max_vertex_size; + m_last_reset_pointer = m_cur_buffer_pointer; m_index_generator.Start(static_cast(index.first)); } diff --git a/Source/Core/VideoBackends/OGL/OGLVertexManager.cpp b/Source/Core/VideoBackends/OGL/OGLVertexManager.cpp index 9c08b238da..13840bcd20 100644 --- a/Source/Core/VideoBackends/OGL/OGLVertexManager.cpp +++ b/Source/Core/VideoBackends/OGL/OGLVertexManager.cpp @@ -166,6 +166,7 @@ void VertexManager::ResetBuffer(u32 vertex_stride) auto buffer = m_vertex_buffer->Map(MAXVBUFFERSIZE, vertex_stride); m_cur_buffer_pointer = m_base_buffer_pointer = buffer.first; m_end_buffer_pointer = buffer.first + MAXVBUFFERSIZE; + m_last_reset_pointer = m_cur_buffer_pointer; buffer = m_index_buffer->Map(MAXIBUFFERSIZE * sizeof(u16)); m_index_generator.Start(reinterpret_cast(buffer.first)); diff --git a/Source/Core/VideoBackends/Vulkan/VKVertexManager.cpp b/Source/Core/VideoBackends/Vulkan/VKVertexManager.cpp index e18a5ffd81..e151a5d380 100644 --- a/Source/Core/VideoBackends/Vulkan/VKVertexManager.cpp +++ b/Source/Core/VideoBackends/Vulkan/VKVertexManager.cpp @@ -171,6 +171,7 @@ void VertexManager::ResetBuffer(u32 vertex_stride) m_end_buffer_pointer = m_vertex_stream_buffer->GetCurrentHostPointer() + MAXVBUFFERSIZE; m_cur_buffer_pointer = m_vertex_stream_buffer->GetCurrentHostPointer(); m_index_generator.Start(reinterpret_cast(m_index_stream_buffer->GetCurrentHostPointer())); + m_last_reset_pointer = m_cur_buffer_pointer; } void VertexManager::CommitBuffer(u32 num_vertices, u32 vertex_stride, u32 num_indices, diff --git a/Source/Core/VideoCommon/Assets/CustomAsset.cpp b/Source/Core/VideoCommon/Assets/CustomAsset.cpp index 1591f93f91..96205b084c 100644 --- a/Source/Core/VideoCommon/Assets/CustomAsset.cpp +++ b/Source/Core/VideoCommon/Assets/CustomAsset.cpp @@ -6,8 +6,8 @@ namespace VideoCommon { CustomAsset::CustomAsset(std::shared_ptr library, - const CustomAssetLibrary::AssetID& asset_id) - : m_owning_library(std::move(library)), m_asset_id(asset_id) + const CustomAssetLibrary::AssetID& asset_id, u64 session_id) + : m_owning_library(std::move(library)), m_asset_id(asset_id), m_session_id(session_id) { } @@ -34,6 +34,11 @@ const CustomAssetLibrary::TimeType& CustomAsset::GetLastLoadedTime() const return m_last_loaded_time; } +std::size_t CustomAsset::GetSessionId() const +{ + return m_session_id; +} + const CustomAssetLibrary::AssetID& CustomAsset::GetAssetId() const { return m_asset_id; diff --git a/Source/Core/VideoCommon/Assets/CustomAsset.h b/Source/Core/VideoCommon/Assets/CustomAsset.h index ea4a182932..85f0471ff7 100644 --- a/Source/Core/VideoCommon/Assets/CustomAsset.h +++ b/Source/Core/VideoCommon/Assets/CustomAsset.h @@ -18,7 +18,7 @@ class CustomAsset { public: CustomAsset(std::shared_ptr library, - const CustomAssetLibrary::AssetID& asset_id); + const CustomAssetLibrary::AssetID& asset_id, u64 session_id); virtual ~CustomAsset() = default; CustomAsset(const CustomAsset&) = delete; CustomAsset(CustomAsset&&) = delete; @@ -39,6 +39,9 @@ public: // Returns an id that uniquely identifies this asset const CustomAssetLibrary::AssetID& GetAssetId() const; + // Returns an id that is unique to this session + std::size_t GetSessionId() const; + // A rough estimate of how much space this asset // will take in memroy std::size_t GetByteSizeInMemory() const; @@ -49,6 +52,7 @@ protected: private: virtual CustomAssetLibrary::LoadInfo LoadImpl(const CustomAssetLibrary::AssetID& asset_id) = 0; CustomAssetLibrary::AssetID m_asset_id; + std::size_t m_session_id; mutable std::mutex m_info_lock; std::size_t m_bytes_loaded = 0; diff --git a/Source/Core/VideoCommon/Assets/CustomAssetLibrary.h b/Source/Core/VideoCommon/Assets/CustomAssetLibrary.h index b4831ea650..c34dfff4a8 100644 --- a/Source/Core/VideoCommon/Assets/CustomAssetLibrary.h +++ b/Source/Core/VideoCommon/Assets/CustomAssetLibrary.h @@ -13,6 +13,9 @@ namespace VideoCommon struct MaterialData; struct MeshData; struct PixelShaderData; +struct RasterMaterialData; +struct RasterShaderData; +struct RenderTargetData; struct TextureData; // This class provides functionality to load @@ -46,11 +49,16 @@ public: // Loads a pixel shader virtual LoadInfo LoadPixelShader(const AssetID& asset_id, PixelShaderData* data) = 0; + virtual LoadInfo LoadShader(const AssetID& asset_id, RasterShaderData* data) = 0; // Loads a material virtual LoadInfo LoadMaterial(const AssetID& asset_id, MaterialData* data) = 0; + virtual LoadInfo LoadMaterial(const AssetID& asset_id, RasterMaterialData* data) = 0; // Loads a mesh virtual LoadInfo LoadMesh(const AssetID& asset_id, MeshData* data) = 0; + + // Loads a render target + virtual LoadInfo LoadRenderTarget(const AssetID& asset_id, RenderTargetData* data) = 0; }; } // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Assets/CustomAssetLoader.cpp b/Source/Core/VideoCommon/Assets/CustomAssetLoader.cpp index 119d7444ea..9d3a311f78 100644 --- a/Source/Core/VideoCommon/Assets/CustomAssetLoader.cpp +++ b/Source/Core/VideoCommon/Assets/CustomAssetLoader.cpp @@ -4,13 +4,17 @@ #include "VideoCommon/Assets/CustomAssetLoader.h" #include "Common/MemoryUtil.h" +#include "Common/Thread.h" + #include "VideoCommon/Assets/CustomAssetLibrary.h" +#include "VideoCommon/VideoEvents.h" namespace VideoCommon { void CustomAssetLoader::Init() { - m_asset_monitor_thread_shutdown.Clear(); + m_frame_event = + AfterFrameEvent::Register([this](Core::System&) { OnFrameEnd(); }, "CustomAssetLoader"); const size_t sys_mem = Common::MemPhysical(); const size_t recommended_min_mem = 2 * size_t(1024 * 1024 * 1024); @@ -18,65 +22,12 @@ void CustomAssetLoader::Init() m_max_memory_available = (sys_mem / 2 < recommended_min_mem) ? (sys_mem / 2) : (sys_mem - recommended_min_mem); - m_asset_monitor_thread = std::thread([this]() { - Common::SetCurrentThreadName("Asset monitor"); - while (true) - { - if (m_asset_monitor_thread_shutdown.IsSet()) - { - break; - } - - std::this_thread::sleep_for(TIME_BETWEEN_ASSET_MONITOR_CHECKS); - - std::lock_guard lk(m_asset_load_lock); - for (auto& [asset_id, asset_to_monitor] : m_assets_to_monitor) - { - if (auto ptr = asset_to_monitor.lock()) - { - const auto write_time = ptr->GetLastWriteTime(); - if (write_time > ptr->GetLastLoadedTime()) - { - (void)ptr->Load(); - } - } - } - } - }); - - m_asset_load_thread.Reset("Custom Asset Loader", [this](std::weak_ptr asset) { - if (auto ptr = asset.lock()) - { - if (m_memory_exceeded) - return; - - if (ptr->Load()) - { - std::lock_guard lk(m_asset_load_lock); - const std::size_t asset_memory_size = ptr->GetByteSizeInMemory(); - m_total_bytes_loaded += asset_memory_size; - m_assets_to_monitor.try_emplace(ptr->GetAssetId(), ptr); - if (m_total_bytes_loaded > m_max_memory_available) - { - ERROR_LOG_FMT(VIDEO, - "Asset memory exceeded with asset '{}', future assets won't load until " - "memory is available.", - ptr->GetAssetId()); - m_memory_exceeded = true; - } - } - } - }); + ResizeWorkerThreads(2); } void CustomAssetLoader ::Shutdown() { - m_asset_load_thread.Shutdown(true); - - m_asset_monitor_thread_shutdown.Set(); - m_asset_monitor_thread.join(); - m_assets_to_monitor.clear(); - m_total_bytes_loaded = 0; + Reset(false); } std::shared_ptr @@ -105,4 +56,233 @@ std::shared_ptr CustomAssetLoader::LoadMesh(const CustomAssetLibrary: { return LoadOrCreateAsset(asset_id, m_meshes, std::move(library)); } + +void CustomAssetLoader::AssetReferenced(u64 asset_session_id) +{ + auto& asset_load_info = m_asset_load_info[asset_session_id]; + asset_load_info.last_xfb_seen = m_xfbs_seen; +} + +void CustomAssetLoader::Reset(bool restart_worker_threads) +{ + const std::size_t worker_thread_count = m_worker_threads.size(); + StopWorkerThreads(); + + m_game_textures.clear(); + m_pixel_shaders.clear(); + m_materials.clear(); + m_meshes.clear(); + + m_assetid_to_asset_index.clear(); + m_asset_load_info.clear(); + + { + std::lock_guard guard(m_reload_work_lock); + m_assetids_to_reload.clear(); + } + + { + std::lock_guard guard(m_pending_work_lock); + m_pending_work_per_frame.clear(); + } + + { + std::lock_guard guard(m_completed_work_lock); + m_completed_work.clear(); + } + + if (restart_worker_threads) + { + StartWorkerThreads(static_cast(worker_thread_count)); + } +} + +void CustomAssetLoader::ReloadAsset(const CustomAssetLibrary::AssetID& asset_id) +{ + std::lock_guard guard(m_reload_work_lock); + m_assetids_to_reload.push_back(asset_id); +} + +bool CustomAssetLoader::StartWorkerThreads(u32 num_worker_threads) +{ + if (num_worker_threads == 0) + return true; + + for (u32 i = 0; i < num_worker_threads; i++) + { + m_worker_thread_start_result.store(false); + + void* thread_param = nullptr; + std::thread thr(&CustomAssetLoader::WorkerThreadEntryPoint, this, thread_param); + m_init_event.Wait(); + + if (!m_worker_thread_start_result.load()) + { + WARN_LOG_FMT(VIDEO, "Failed to start asset load worker thread."); + thr.join(); + break; + } + + m_worker_threads.push_back(std::move(thr)); + } + + return HasWorkerThreads(); +} + +bool CustomAssetLoader::ResizeWorkerThreads(u32 num_worker_threads) +{ + if (m_worker_threads.size() == num_worker_threads) + return true; + + StopWorkerThreads(); + return StartWorkerThreads(num_worker_threads); +} + +bool CustomAssetLoader::HasWorkerThreads() const +{ + return !m_worker_threads.empty(); +} + +void CustomAssetLoader::StopWorkerThreads() +{ + if (!HasWorkerThreads()) + return; + + // Signal worker threads to stop, and wake all of them. + { + std::lock_guard guard(m_pending_work_lock); + m_exit_flag.Set(); + m_worker_thread_wake.notify_all(); + } + + // Wait for worker threads to exit. + for (std::thread& thr : m_worker_threads) + thr.join(); + m_worker_threads.clear(); + m_exit_flag.Clear(); +} + +void CustomAssetLoader::WorkerThreadEntryPoint(void* param) +{ + Common::SetCurrentThreadName("Asset Loader Worker"); + + m_worker_thread_start_result.store(true); + m_init_event.Set(); + + WorkerThreadRun(); +} + +void CustomAssetLoader::WorkerThreadRun() +{ + std::unique_lock pending_lock(m_pending_work_lock); + while (!m_exit_flag.IsSet()) + { + m_worker_thread_wake.wait(pending_lock); + + while (!m_pending_work_per_frame.empty() && !m_exit_flag.IsSet()) + { + m_busy_workers++; + + auto pending_iter = m_pending_work_per_frame.begin(); + auto item(std::move(pending_iter->second)); + m_pending_work_per_frame.erase(pending_iter); + + const auto item_shared = item.lock(); + pending_lock.unlock(); + + if (item_shared) + { + if (item_shared->Load()) + { + std::lock_guard completed_guard(m_completed_work_lock); + m_completed_work.push_back(item_shared->GetSessionId()); + } + } + + pending_lock.lock(); + m_busy_workers--; + } + } +} + +void CustomAssetLoader::OnFrameEnd() +{ + std::vector assetids_to_reload; + { + std::lock_guard guard(m_reload_work_lock); + m_assetids_to_reload.swap(assetids_to_reload); + } + for (const CustomAssetLibrary::AssetID& asset_id : assetids_to_reload) + { + if (const auto asset_session_id_iter = m_assetid_to_asset_index.find(asset_id); + asset_session_id_iter != m_assetid_to_asset_index.end()) + { + auto& asset_load_info = m_asset_load_info[asset_session_id_iter->second]; + asset_load_info.xfb_load_request = m_xfbs_seen; + } + } + + std::vector completed_work; + { + std::lock_guard guard(m_completed_work_lock); + m_completed_work.swap(completed_work); + } + for (std::size_t completed_index : completed_work) + { + auto& asset_load_info = m_asset_load_info[completed_index]; + + // If we had a load request and it wasn't from this frame, clear it + if (asset_load_info.xfb_load_request && asset_load_info.xfb_load_request != m_xfbs_seen) + { + asset_load_info.xfb_load_request = std::nullopt; + } + } + + m_xfbs_seen++; + + std::size_t total_bytes_loaded = 0; + + // Build up the work prioritizing newest requested assets first + PendingWorkContainer new_pending_work; + for (const auto& asset_load_info : m_asset_load_info) + { + if (const auto asset = asset_load_info.asset.lock()) + { + total_bytes_loaded += asset->GetByteSizeInMemory(); + if (total_bytes_loaded > m_max_memory_available) + { + if (!m_memory_exceeded) + { + m_memory_exceeded = true; + ERROR_LOG_FMT(VIDEO, + "Asset memory exceeded with asset '{}', future assets won't load until " + "memory is available.", + asset->GetAssetId()); + } + break; + } + if (asset_load_info.xfb_load_request) + { + new_pending_work.emplace(asset_load_info.last_xfb_seen, asset_load_info.asset); + } + } + } + + if (m_memory_exceeded && total_bytes_loaded <= m_max_memory_available) + { + INFO_LOG_FMT(VIDEO, "Asset memory went below limit, new assets can begin loading."); + m_memory_exceeded = false; + } + + if (new_pending_work.empty()) + return; + + // Now notify our workers + { + std::lock_guard guard(m_pending_work_lock); + std::swap(m_pending_work_per_frame, new_pending_work); + m_worker_thread_wake.notify_all(); + } +} + } // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Assets/CustomAssetLoader.h b/Source/Core/VideoCommon/Assets/CustomAssetLoader.h index 90d4f81a0e..efba97ecd0 100644 --- a/Source/Core/VideoCommon/Assets/CustomAssetLoader.h +++ b/Source/Core/VideoCommon/Assets/CustomAssetLoader.h @@ -4,19 +4,24 @@ #pragma once #include +#include #include #include #include +#include #include +#include +#include "Common/Event.h" #include "Common/Flag.h" +#include "Common/HookableEvent.h" #include "Common/Logging/Log.h" -#include "Common/WorkQueueThread.h" #include "VideoCommon/Assets/CustomAsset.h" #include "VideoCommon/Assets/MaterialAsset.h" #include "VideoCommon/Assets/MeshAsset.h" #include "VideoCommon/Assets/ShaderAsset.h" #include "VideoCommon/Assets/TextureAsset.h" +#include "VideoCommon/Present.h" namespace VideoCommon { @@ -52,6 +57,14 @@ public: std::shared_ptr LoadMesh(const CustomAssetLibrary::AssetID& asset_id, std::shared_ptr library); + // Notifies the asset system that an asset has been used + void AssetReferenced(u64 asset_session_id); + + // Requests that an asset that exists be reloaded + void ReloadAsset(const CustomAssetLibrary::AssetID& asset_id); + + void Reset(bool restart_worker_threads = true); + private: // TODO C++20: use a 'derived_from' concept against 'CustomAsset' when available template @@ -65,44 +78,87 @@ private: { auto shared = it->second.lock(); if (shared) - return shared; - } - std::shared_ptr ptr(new AssetType(std::move(library), asset_id), [&](AssetType* a) { { - std::lock_guard lk(m_asset_load_lock); - m_total_bytes_loaded -= a->GetByteSizeInMemory(); - m_assets_to_monitor.erase(a->GetAssetId()); - if (m_max_memory_available >= m_total_bytes_loaded && m_memory_exceeded) - { - INFO_LOG_FMT(VIDEO, "Asset memory went below limit, new assets can begin loading."); - m_memory_exceeded = false; - } + auto& asset_load_info = m_asset_load_info[shared->GetSessionId()]; + asset_load_info.last_xfb_seen = m_xfbs_seen; + asset_load_info.xfb_load_request = m_xfbs_seen; + return shared; } - delete a; - }); + } + + const auto [index_it, index_inserted] = m_assetid_to_asset_index.try_emplace(asset_id, 0); + if (index_inserted) + { + index_it->second = m_asset_load_info.size(); + } + + auto ptr = std::make_shared(std::move(library), asset_id, index_it->second); it->second = ptr; - m_asset_load_thread.Push(it->second); + + AssetLoadInfo* asset_load_info; + if (index_inserted) + { + m_asset_load_info.emplace_back(); + asset_load_info = &m_asset_load_info.back(); + } + else + { + asset_load_info = &m_asset_load_info[index_it->second]; + } + + asset_load_info->asset = ptr; + asset_load_info->last_xfb_seen = m_xfbs_seen; + asset_load_info->xfb_load_request = m_xfbs_seen; return ptr; } - static constexpr auto TIME_BETWEEN_ASSET_MONITOR_CHECKS = std::chrono::milliseconds{500}; + bool StartWorkerThreads(u32 num_worker_threads); + bool ResizeWorkerThreads(u32 num_worker_threads); + bool HasWorkerThreads() const; + void StopWorkerThreads(); + + void WorkerThreadEntryPoint(void* param); + void WorkerThreadRun(); + + void OnFrameEnd(); + + Common::EventHook m_frame_event; std::map> m_game_textures; std::map> m_pixel_shaders; std::map> m_materials; std::map> m_meshes; - std::thread m_asset_monitor_thread; - Common::Flag m_asset_monitor_thread_shutdown; - std::size_t m_total_bytes_loaded = 0; + std::map m_assetid_to_asset_index; + + struct AssetLoadInfo + { + std::weak_ptr asset; + u64 last_xfb_seen = 0; + std::optional xfb_load_request; + }; + std::vector m_asset_load_info; + u64 m_xfbs_seen = 0; + std::size_t m_max_memory_available = 0; - std::atomic_bool m_memory_exceeded = false; + bool m_memory_exceeded = false; - std::map> m_assets_to_monitor; + Common::Flag m_exit_flag; + Common::Event m_init_event; - // Use a recursive mutex to handle the scenario where an asset goes out of scope while - // iterating over the assets to monitor which calls the lock above in 'LoadOrCreateAsset' - std::recursive_mutex m_asset_load_lock; - Common::WorkQueueThread> m_asset_load_thread; + std::vector m_worker_threads; + std::atomic_bool m_worker_thread_start_result{false}; + + using PendingWorkContainer = std::multimap, std::greater<>>; + PendingWorkContainer m_pending_work_per_frame; + std::mutex m_pending_work_lock; + std::condition_variable m_worker_thread_wake; + std::atomic_size_t m_busy_workers{0}; + + std::vector m_completed_work; + std::mutex m_completed_work_lock; + + std::vector m_assetids_to_reload; + std::mutex m_reload_work_lock; }; } // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Assets/CustomAssetLoader2.cpp b/Source/Core/VideoCommon/Assets/CustomAssetLoader2.cpp new file mode 100644 index 0000000000..3bb03fa9e7 --- /dev/null +++ b/Source/Core/VideoCommon/Assets/CustomAssetLoader2.cpp @@ -0,0 +1,175 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/Assets/CustomAssetLoader2.h" + +#include "Common/Logging/Log.h" +#include "Common/Thread.h" + +namespace VideoCommon +{ +void CustomAssetLoader2::Initialize() +{ + ResizeWorkerThreads(2); +} + +void CustomAssetLoader2 ::Shutdown() +{ + Reset(false); +} + +void CustomAssetLoader2::Reset(bool restart_worker_threads) +{ + const std::size_t worker_thread_count = m_worker_threads.size(); + StopWorkerThreads(); + + { + std::lock_guard guard(m_pending_work_lock); + m_pending_assets.clear(); + m_max_memory_allowed = 0; + m_current_asset_memory = 0; + } + + { + std::lock_guard guard(m_completed_work_lock); + m_completed_asset_session_ids.clear(); + m_completed_asset_memory = 0; + } + + if (restart_worker_threads) + { + StartWorkerThreads(static_cast(worker_thread_count)); + } +} + +bool CustomAssetLoader2::StartWorkerThreads(u32 num_worker_threads) +{ + if (num_worker_threads == 0) + return true; + + for (u32 i = 0; i < num_worker_threads; i++) + { + m_worker_thread_start_result.store(false); + + void* thread_param = nullptr; + std::thread thr(&CustomAssetLoader2::WorkerThreadEntryPoint, this, thread_param); + m_init_event.Wait(); + + if (!m_worker_thread_start_result.load()) + { + WARN_LOG_FMT(VIDEO, "Failed to start asset load worker thread."); + thr.join(); + break; + } + + m_worker_threads.push_back(std::move(thr)); + } + + return HasWorkerThreads(); +} + +bool CustomAssetLoader2::ResizeWorkerThreads(u32 num_worker_threads) +{ + if (m_worker_threads.size() == num_worker_threads) + return true; + + StopWorkerThreads(); + return StartWorkerThreads(num_worker_threads); +} + +bool CustomAssetLoader2::HasWorkerThreads() const +{ + return !m_worker_threads.empty(); +} + +void CustomAssetLoader2::StopWorkerThreads() +{ + if (!HasWorkerThreads()) + return; + + // Signal worker threads to stop, and wake all of them. + { + std::lock_guard guard(m_pending_work_lock); + m_exit_flag.Set(); + m_worker_thread_wake.notify_all(); + } + + // Wait for worker threads to exit. + for (std::thread& thr : m_worker_threads) + thr.join(); + m_worker_threads.clear(); + m_exit_flag.Clear(); +} + +void CustomAssetLoader2::WorkerThreadEntryPoint(void* param) +{ + Common::SetCurrentThreadName("Asset Loader Worker"); + + m_worker_thread_start_result.store(true); + m_init_event.Set(); + + WorkerThreadRun(); +} + +void CustomAssetLoader2::WorkerThreadRun() +{ + std::unique_lock pending_lock(m_pending_work_lock); + while (!m_exit_flag.IsSet()) + { + m_worker_thread_wake.wait(pending_lock); + + while (!m_pending_assets.empty() && !m_exit_flag.IsSet()) + { + auto pending_iter = m_pending_assets.begin(); + const auto item = *pending_iter; + m_pending_assets.erase(pending_iter); + + if ((m_current_asset_memory + m_completed_asset_memory) > m_max_memory_allowed) + break; + + pending_lock.unlock(); + if (item->Load()) + { + std::lock_guard completed_guard(m_completed_work_lock); + m_completed_asset_memory += item->GetByteSizeInMemory(); + m_completed_asset_session_ids.push_back(item->GetSessionId()); + } + + pending_lock.lock(); + } + } +} + +std::vector +CustomAssetLoader2::LoadAssets(const std::list& pending_assets, + u64 current_loaded_memory, u64 max_memory_allowed) +{ + u64 total_memory = current_loaded_memory; + std::vector completed_asset_session_ids; + { + std::lock_guard guard(m_completed_work_lock); + m_completed_asset_session_ids.swap(completed_asset_session_ids); + total_memory += m_completed_asset_memory; + m_completed_asset_memory = 0; + } + + if (pending_assets.empty()) + return completed_asset_session_ids; + + if (total_memory > max_memory_allowed) + return completed_asset_session_ids; + + // There's new assets to process, notify worker threads + { + std::lock_guard guard(m_pending_work_lock); + m_pending_assets = pending_assets; + m_current_asset_memory = total_memory; + m_max_memory_allowed = max_memory_allowed; + if (m_current_asset_memory < m_max_memory_allowed) + m_worker_thread_wake.notify_all(); + } + + return completed_asset_session_ids; +} + +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Assets/CustomAssetLoader2.h b/Source/Core/VideoCommon/Assets/CustomAssetLoader2.h new file mode 100644 index 0000000000..1a498a60ca --- /dev/null +++ b/Source/Core/VideoCommon/Assets/CustomAssetLoader2.h @@ -0,0 +1,65 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "Common/Event.h" +#include "Common/Flag.h" +#include "VideoCommon/Assets/CustomAsset.h" + +namespace VideoCommon +{ +class CustomAssetLoader2 +{ +public: + CustomAssetLoader2() = default; + ~CustomAssetLoader2() = default; + CustomAssetLoader2(const CustomAssetLoader2&) = delete; + CustomAssetLoader2(CustomAssetLoader2&&) = delete; + CustomAssetLoader2& operator=(const CustomAssetLoader2&) = delete; + CustomAssetLoader2& operator=(CustomAssetLoader2&&) = delete; + + void Initialize(); + void Shutdown(); + + // Returns a vector of asset session ids that were loaded in the last frame + std::vector LoadAssets(const std::list& pending_assets, + u64 current_loaded_memory, u64 max_memory_allowed); + + void Reset(bool restart_worker_threads = true); + +private: + bool StartWorkerThreads(u32 num_worker_threads); + bool ResizeWorkerThreads(u32 num_worker_threads); + bool HasWorkerThreads() const; + void StopWorkerThreads(); + + void WorkerThreadEntryPoint(void* param); + void WorkerThreadRun(); + + Common::Flag m_exit_flag; + Common::Event m_init_event; + + std::vector m_worker_threads; + std::atomic_bool m_worker_thread_start_result{false}; + + std::list m_pending_assets; + std::atomic m_current_asset_memory = 0; + u64 m_max_memory_allowed = 0; + std::mutex m_pending_work_lock; + + std::condition_variable m_worker_thread_wake; + + std::vector m_completed_asset_session_ids; + std::atomic m_completed_asset_memory = 0; + std::mutex m_completed_work_lock; +}; +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.cpp b/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.cpp index 90d77d8ec9..ca12a8bbbe 100644 --- a/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.cpp +++ b/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.cpp @@ -13,10 +13,13 @@ #include "Common/JsonUtil.h" #include "Common/Logging/Log.h" #include "Common/StringUtil.h" +#include "Core/System.h" #include "VideoCommon/Assets/MaterialAsset.h" #include "VideoCommon/Assets/MeshAsset.h" +#include "VideoCommon/Assets/RenderTargetAsset.h" #include "VideoCommon/Assets/ShaderAsset.h" #include "VideoCommon/Assets/TextureAsset.h" +#include "VideoCommon/GraphicsModSystem/Runtime/CustomResourceManager.h" #include "VideoCommon/RenderState.h" namespace VideoCommon @@ -53,7 +56,7 @@ std::size_t GetAssetSize(const CustomTextureData& data) CustomAssetLibrary::TimeType DirectFilesystemAssetLibrary::GetLastAssetWriteTime(const AssetID& asset_id) const { - std::lock_guard lk(m_lock); + std::lock_guard lk(m_asset_map_lock); if (auto iter = m_assetid_to_asset_map_path.find(asset_id); iter != m_assetid_to_asset_map_path.end()) { @@ -161,6 +164,118 @@ CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadPixelShader(const return LoadInfo{approx_mem_size, GetLastAssetWriteTime(asset_id)}; } +CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadShader(const AssetID& asset_id, + RasterShaderData* data) +{ + const auto asset_map = GetAssetMapForID(asset_id); + + // Asset map for a pixel shader is the shader and some metadata + if (asset_map.size() != 3) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' expected to have three files mapped!", asset_id); + return {}; + } + + const auto metadata = asset_map.find("metadata"); + if (metadata == asset_map.end()) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' expected to have a metadata entry mapped!", asset_id); + return {}; + } + + const auto vertex_shader = asset_map.find("vertex_shader"); + if (vertex_shader == asset_map.end()) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' expected to have a vertex shader entry mapped!", asset_id); + return {}; + } + + const auto pixel_shader = asset_map.find("pixel_shader"); + if (pixel_shader == asset_map.end()) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' expected to have a pixel shader entry mapped!", asset_id); + return {}; + } + + std::size_t metadata_size; + { + std::error_code ec; + metadata_size = std::filesystem::file_size(metadata->second, ec); + if (ec) + { + ERROR_LOG_FMT(VIDEO, + "Asset '{}' error - failed to get shader metadata file size with error '{}'!", + asset_id, ec); + return {}; + } + } + std::size_t vertex_shader_size; + { + std::error_code ec; + vertex_shader_size = std::filesystem::file_size(vertex_shader->second, ec); + if (ec) + { + ERROR_LOG_FMT( + VIDEO, "Asset '{}' error - failed to get vertex shader source file size with error '{}'!", + asset_id, ec); + return {}; + } + } + std::size_t pixel_shader_size; + { + std::error_code ec; + pixel_shader_size = std::filesystem::file_size(pixel_shader->second, ec); + if (ec) + { + ERROR_LOG_FMT( + VIDEO, "Asset '{}' error - failed to get pixel shader source file size with error '{}'!", + asset_id, ec); + return {}; + } + } + const auto approx_mem_size = metadata_size + vertex_shader_size + pixel_shader_size; + + if (!File::ReadFileToString(PathToString(vertex_shader->second), data->m_vertex_source)) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' error - failed to load the vertex shader file '{}',", + asset_id, PathToString(vertex_shader->second)); + return {}; + } + + if (!File::ReadFileToString(PathToString(pixel_shader->second), data->m_pixel_source)) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' error - failed to load the pixel shader file '{}',", asset_id, + PathToString(pixel_shader->second)); + return {}; + } + + picojson::value root; + std::string error; + if (!JsonFromFile(PathToString(metadata->second), &root, &error)) + { + ERROR_LOG_FMT(VIDEO, + "Asset '{}' error - failed to load the json file '{}', due to parse error: {}", + asset_id, PathToString(metadata->second), error); + return {}; + } + + if (!root.is()) + { + ERROR_LOG_FMT( + VIDEO, + "Asset '{}' error - failed to load the json file '{}', due to root not being an object!", + asset_id, PathToString(metadata->second)); + return {}; + } + + const auto& root_obj = root.get(); + + if (!RasterShaderData::FromJson(asset_id, root_obj, data)) + return {}; + + return LoadInfo{approx_mem_size, GetLastAssetWriteTime(asset_id)}; +} + CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadMaterial(const AssetID& asset_id, MaterialData* data) { @@ -219,6 +334,64 @@ CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadMaterial(const As return LoadInfo{metadata_size, GetLastAssetWriteTime(asset_id)}; } +CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadMaterial(const AssetID& asset_id, + RasterMaterialData* data) +{ + const auto asset_map = GetAssetMapForID(asset_id); + + // Material is expected to have one asset mapped + if (asset_map.empty() || asset_map.size() > 1) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' error - material expected to have one file mapped!", asset_id); + return {}; + } + const auto& asset_path = asset_map.begin()->second; + + std::size_t metadata_size; + { + std::error_code ec; + metadata_size = std::filesystem::file_size(asset_path, ec); + if (ec) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' error - failed to get material file size with error '{}'!", + asset_id, ec); + return {}; + } + } + + picojson::value root; + std::string error; + if (!JsonFromFile(PathToString(asset_path), &root, &error)) + { + ERROR_LOG_FMT( + VIDEO, + "Asset '{}' error - material failed to load the json file '{}', due to parse error: {}", + asset_id, PathToString(asset_path), error); + return {}; + } + if (!root.is()) + { + ERROR_LOG_FMT(VIDEO, + "Asset '{}' error - material failed to load the json file '{}', due to root not " + "being an object!", + asset_id, PathToString(asset_path)); + return {}; + } + + const auto& root_obj = root.get(); + + if (!RasterMaterialData::FromJson(asset_id, root_obj, data)) + { + ERROR_LOG_FMT(VIDEO, + "Asset '{}' error - material failed to load the json file '{}', as material " + "json could not be parsed!", + asset_id, PathToString(asset_path)); + return {}; + } + + return LoadInfo{metadata_size, GetLastAssetWriteTime(asset_id)}; +} + CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadMesh(const AssetID& asset_id, MeshData* data) { @@ -314,6 +487,68 @@ CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadMesh(const AssetI return LoadInfo{approx_mem_size, GetLastAssetWriteTime(asset_id)}; } +CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadRenderTarget(const AssetID& asset_id, + RenderTargetData* data) +{ + const auto asset_map = GetAssetMapForID(asset_id); + + // Material is expected to have one asset mapped + if (asset_map.empty() || asset_map.size() > 1) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' error - render target expected to have one file mapped!", + asset_id); + return {}; + } + const auto& asset_path = asset_map.begin()->second; + + std::size_t metadata_size; + { + std::error_code ec; + metadata_size = std::filesystem::file_size(asset_path, ec); + if (ec) + { + ERROR_LOG_FMT(VIDEO, + "Asset '{}' error - failed to get render target file size with error '{}'!", + asset_id, ec); + return {}; + } + } + + picojson::value root; + std::string error; + if (!JsonFromFile(PathToString(asset_path), &root, &error)) + { + ERROR_LOG_FMT(VIDEO, + "Asset '{}' error - render target failed to load the json file '{}', due to " + "parse error: {}", + asset_id, PathToString(asset_path), error); + return {}; + } + if (!root.is()) + { + ERROR_LOG_FMT( + VIDEO, + "Asset '{}' error - render target failed to load the json file '{}', due to root not " + "being an object!", + asset_id, PathToString(asset_path)); + return {}; + } + + const auto& root_obj = root.get(); + + if (!RenderTargetData::FromJson(asset_id, root_obj, data)) + { + ERROR_LOG_FMT( + VIDEO, + "Asset '{}' error - render target failed to load the json file '{}', as render target " + "json could not be parsed!", + asset_id, PathToString(asset_path)); + return {}; + } + + return LoadInfo{metadata_size, GetLastAssetWriteTime(asset_id)}; +} + CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadTexture(const AssetID& asset_id, TextureData* data) { @@ -434,10 +669,42 @@ CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadTexture(const Ass } void DirectFilesystemAssetLibrary::SetAssetIDMapData(const AssetID& asset_id, - AssetMap asset_path_map) + VideoCommon::Assets::AssetMap asset_path_map) { - std::lock_guard lk(m_lock); - m_assetid_to_asset_map_path[asset_id] = std::move(asset_path_map); + VideoCommon::Assets::AssetMap previous_asset_map; + { + std::lock_guard lk(m_asset_map_lock); + previous_asset_map = m_assetid_to_asset_map_path[asset_id]; + } + + { + std::lock_guard lk(m_path_map_lock); + for (const auto& [name, path] : previous_asset_map) + { + m_path_to_asset_id.erase(PathToString(path)); + } + + for (const auto& [name, path] : asset_path_map) + { + m_path_to_asset_id[PathToString(path)] = asset_id; + } + } + + { + std::lock_guard lk(m_asset_map_lock); + m_assetid_to_asset_map_path[asset_id] = std::move(asset_path_map); + } +} + +void DirectFilesystemAssetLibrary::PathModified(std::string_view path) +{ + std::lock_guard lk(m_path_map_lock); + if (const auto iter = m_path_to_asset_id.find(path); iter != m_path_to_asset_id.end()) + { + auto& system = Core::System::GetInstance(); + auto& resource_manager = system.GetCustomResourceManager(); + resource_manager.ReloadAsset(iter->second); + } } bool DirectFilesystemAssetLibrary::LoadMips(const std::filesystem::path& asset_path, @@ -492,10 +759,10 @@ bool DirectFilesystemAssetLibrary::LoadMips(const std::filesystem::path& asset_p return true; } -DirectFilesystemAssetLibrary::AssetMap +VideoCommon::Assets::AssetMap DirectFilesystemAssetLibrary::GetAssetMapForID(const AssetID& asset_id) const { - std::lock_guard lk(m_lock); + std::lock_guard lk(m_asset_map_lock); if (auto iter = m_assetid_to_asset_map_path.find(asset_id); iter != m_assetid_to_asset_map_path.end()) { diff --git a/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.h b/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.h index c4d99baf82..046d7b513f 100644 --- a/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.h +++ b/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.h @@ -8,22 +8,24 @@ #include #include -#include "VideoCommon/Assets/CustomAssetLibrary.h" #include "VideoCommon/Assets/CustomTextureData.h" +#include "VideoCommon/Assets/Types.h" +#include "VideoCommon/Assets/WatchableFilesystemAssetLibrary.h" namespace VideoCommon { // This class implements 'CustomAssetLibrary' and loads any assets // directly from the filesystem -class DirectFilesystemAssetLibrary final : public CustomAssetLibrary +class DirectFilesystemAssetLibrary final : public WatchableFilesystemAssetLibrary { public: - using AssetMap = std::map; - LoadInfo LoadTexture(const AssetID& asset_id, TextureData* data) override; LoadInfo LoadPixelShader(const AssetID& asset_id, PixelShaderData* data) override; + LoadInfo LoadShader(const AssetID& asset_id, RasterShaderData* data) override; LoadInfo LoadMaterial(const AssetID& asset_id, MaterialData* data) override; + LoadInfo LoadMaterial(const AssetID& asset_id, RasterMaterialData* data) override; LoadInfo LoadMesh(const AssetID& asset_id, MeshData* data) override; + LoadInfo LoadRenderTarget(const AssetID& asset_id, RenderTargetData* data) override; // Gets the latest time from amongst all the files in the asset map TimeType GetLastAssetWriteTime(const AssetID& asset_id) const override; @@ -31,16 +33,21 @@ public: // Assigns the asset id to a map of files, how this map is read is dependent on the data // For instance, a raw texture would expect the map to have a single entry and load that // file as the asset. But a model file data might have its data spread across multiple files - void SetAssetIDMapData(const AssetID& asset_id, AssetMap asset_path_map); + void SetAssetIDMapData(const AssetID& asset_id, Assets::AssetMap asset_path_map); private: + void PathModified(std::string_view path) override; + // Loads additional mip levels into the texture structure until _mip texture is not found bool LoadMips(const std::filesystem::path& asset_path, CustomTextureData::ArraySlice* data); // Gets the asset map given an asset id - AssetMap GetAssetMapForID(const AssetID& asset_id) const; + Assets::AssetMap GetAssetMapForID(const AssetID& asset_id) const; - mutable std::mutex m_lock; - std::map> m_assetid_to_asset_map_path; + mutable std::mutex m_asset_map_lock; + std::map m_assetid_to_asset_map_path; + + mutable std::mutex m_path_map_lock; + std::map> m_path_to_asset_id; }; } // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Assets/MaterialAsset.cpp b/Source/Core/VideoCommon/Assets/MaterialAsset.cpp index a9f2940deb..7422db2b88 100644 --- a/Source/Core/VideoCommon/Assets/MaterialAsset.cpp +++ b/Source/Core/VideoCommon/Assets/MaterialAsset.cpp @@ -130,7 +130,18 @@ bool ParsePropertyValue(const CustomAssetLibrary::AssetID& asset_id, { if (json_value.is()) { - *value = json_value.get(); + TextureSamplerValue texture_sampler_value; + texture_sampler_value.asset = json_value.get(); + *value = texture_sampler_value; + return true; + } + else if (json_value.is()) + { + auto texture_asset_obj = json_value.get(); + TextureSamplerValue texture_sampler_value; + if (!TextureSamplerValue::FromJson(texture_asset_obj, &texture_sampler_value)) + return false; + *value = texture_sampler_value; return true; } } @@ -207,6 +218,164 @@ bool ParseMaterialProperties(const CustomAssetLibrary::AssetID& asset_id, return true; } + +template +bool ParseNumeric(const CustomAssetLibrary::AssetID& asset_id, const picojson::value& json_value, + MaterialProperty2::Value* value) +{ + static_assert(ElementCount <= 4, "Numeric data expected to be four elements or less"); + if constexpr (ElementCount == 1) + { + if (!json_value.is()) + { + ERROR_LOG_FMT(VIDEO, + "Asset id '{}' material has attribute where " + "a double was expected but not provided.", + asset_id); + return false; + } + + *value = static_cast(json_value.get()); + } + else + { + if (!json_value.is()) + { + ERROR_LOG_FMT(VIDEO, + "Asset id '{}' material has attribute where " + "an array was expected but not provided.", + asset_id); + return false; + } + + const auto json_data = json_value.get(); + + if (json_data.size() != ElementCount) + { + ERROR_LOG_FMT(VIDEO, + "Asset id '{}' material has attribute with incorrect number " + "of elements, expected {}", + asset_id, ElementCount); + return false; + } + + if (!std::all_of(json_data.begin(), json_data.end(), + [](const picojson::value& v) { return v.is(); })) + { + ERROR_LOG_FMT(VIDEO, + "Asset id '{}' material has attribute where " + "all elements are not of type double.", + asset_id); + return false; + } + + std::array data; + for (std::size_t i = 0; i < ElementCount; i++) + { + data[i] = static_cast(json_data[i].get()); + } + *value = std::move(data); + } + + return true; +} +bool ParsePropertyValue(const CustomAssetLibrary::AssetID& asset_id, + const picojson::value& json_value, std::string_view type, + MaterialProperty2::Value* value) +{ + if (type == "int") + { + return ParseNumeric(asset_id, json_value, value); + } + else if (type == "int2") + { + return ParseNumeric(asset_id, json_value, value); + } + else if (type == "int3") + { + return ParseNumeric(asset_id, json_value, value); + } + else if (type == "int4") + { + return ParseNumeric(asset_id, json_value, value); + } + else if (type == "float") + { + return ParseNumeric(asset_id, json_value, value); + } + else if (type == "float2") + { + return ParseNumeric(asset_id, json_value, value); + } + else if (type == "float3") + { + return ParseNumeric(asset_id, json_value, value); + } + else if (type == "float4") + { + return ParseNumeric(asset_id, json_value, value); + } + else if (type == "bool") + { + if (json_value.is()) + { + *value = json_value.get(); + return true; + } + } + + ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse the json, value is not valid for type '{}'", + asset_id, type); + return false; +} + +bool ParseMaterialProperties(const CustomAssetLibrary::AssetID& asset_id, + const picojson::array& values_data, + std::vector* material_property) +{ + for (const auto& value_data : values_data) + { + VideoCommon::MaterialProperty2 property; + if (!value_data.is()) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse the json, value is not the right json type", + asset_id); + return false; + } + const auto& value_data_obj = value_data.get(); + + const auto type_iter = value_data_obj.find("type"); + if (type_iter == value_data_obj.end()) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse the json, value entry 'type' not found", + asset_id); + return false; + } + if (!type_iter->second.is()) + { + ERROR_LOG_FMT(VIDEO, + "Asset '{}' failed to parse the json, value entry 'type' is not " + "the right json type", + asset_id); + return false; + } + std::string type = type_iter->second.to_str(); + Common::ToLower(&type); + + const auto value_iter = value_data_obj.find("value"); + if (value_iter != value_data_obj.end()) + { + if (!ParsePropertyValue(asset_id, value_iter->second, type, &property.m_value)) + { + return false; + } + } + + material_property->push_back(std::move(property)); + } + + return true; +} } // namespace void MaterialProperty::WriteToMemory(u8*& buffer, const MaterialProperty& property) @@ -218,8 +387,7 @@ void MaterialProperty::WriteToMemory(u8*& buffer, const MaterialProperty& proper }; std::visit( overloaded{ - [&](const CustomAssetLibrary::AssetID&) {}, - [&](s32 value) { write_memory(&value, sizeof(s32)); }, + [&](const TextureSamplerValue&) {}, [&](s32 value) { write_memory(&value, sizeof(s32)); }, [&](const std::array& value) { write_memory(value.data(), sizeof(s32) * 2); }, [&](const std::array& value) { write_memory(value.data(), sizeof(s32) * 3); }, [&](const std::array& value) { write_memory(value.data(), sizeof(s32) * 4); }, @@ -234,8 +402,7 @@ void MaterialProperty::WriteToMemory(u8*& buffer, const MaterialProperty& proper std::size_t MaterialProperty::GetMemorySize(const MaterialProperty& property) { std::size_t result = 0; - std::visit(overloaded{[&](const CustomAssetLibrary::AssetID&) {}, - [&](s32) { result = MemorySize; }, + std::visit(overloaded{[&](const TextureSamplerValue&) {}, [&](s32) { result = MemorySize; }, [&](const std::array&) { result = MemorySize; }, [&](const std::array&) { result = MemorySize; }, [&](const std::array&) { result = MemorySize; }, @@ -267,7 +434,7 @@ void MaterialProperty::WriteAsShaderCode(ShaderCode& shader_source, shader_source.Write("{} {}_padding_{};\n", type, property.m_code_name, i + 1); } }; - std::visit(overloaded{[&](const CustomAssetLibrary::AssetID&) {}, + std::visit(overloaded{[&](const TextureSamplerValue&) {}, [&](s32 value) { write_shader("int", 1); }, [&](const std::array& value) { write_shader("int", 2); }, [&](const std::array& value) { write_shader("int", 3); }, @@ -280,6 +447,44 @@ void MaterialProperty::WriteAsShaderCode(ShaderCode& shader_source, property.m_value); } +void MaterialProperty2::WriteToMemory(u8*& buffer, const MaterialProperty2& property) +{ + const auto write_memory = [&](const void* raw_value, std::size_t data_size) { + std::memcpy(buffer, raw_value, data_size); + std::memset(buffer + data_size, 0, MemorySize - data_size); + buffer += MemorySize; + }; + std::visit( + overloaded{ + [&](s32 value) { write_memory(&value, sizeof(s32)); }, + [&](const std::array& value) { write_memory(value.data(), sizeof(s32) * 2); }, + [&](const std::array& value) { write_memory(value.data(), sizeof(s32) * 3); }, + [&](const std::array& value) { write_memory(value.data(), sizeof(s32) * 4); }, + [&](float value) { write_memory(&value, sizeof(float)); }, + [&](const std::array& value) { write_memory(value.data(), sizeof(float) * 2); }, + [&](const std::array& value) { write_memory(value.data(), sizeof(float) * 3); }, + [&](const std::array& value) { write_memory(value.data(), sizeof(float) * 4); }, + [&](bool value) { write_memory(&value, sizeof(bool)); }}, + property.m_value); +} + +std::size_t MaterialProperty2::GetMemorySize(const MaterialProperty2& property) +{ + std::size_t result = 0; + std::visit(overloaded{[&](s32 value) { result = MemorySize; }, + [&](const std::array&) { result = MemorySize; }, + [&](const std::array&) { result = MemorySize; }, + [&](const std::array&) { result = MemorySize; }, + [&](float) { result = MemorySize; }, + [&](const std::array&) { result = MemorySize; }, + [&](const std::array&) { result = MemorySize; }, + [&](const std::array&) { result = MemorySize; }, + [&](bool) { result = MemorySize; }}, + property.m_value); + + return result; +} + bool MaterialData::FromJson(const CustomAssetLibrary::AssetID& asset_id, const picojson::object& json, MaterialData* data) { @@ -331,9 +536,11 @@ void MaterialData::ToJson(picojson::object* obj, const MaterialData& data) picojson::object json_property; json_property["code_name"] = picojson::value{property.m_code_name}; - std::visit(overloaded{[&](const CustomAssetLibrary::AssetID& value) { + std::visit(overloaded{[&](const TextureSamplerValue& value) { json_property["type"] = picojson::value{"texture_asset"}; - json_property["value"] = picojson::value{value}; + picojson::object value_json; + TextureSamplerValue::ToJson(&value_json, value); + json_property["value"] = picojson::value{value_json}; }, [&](s32 value) { json_property["type"] = picojson::value{"int"}; @@ -380,6 +587,296 @@ void MaterialData::ToJson(picojson::object* obj, const MaterialData& data) json_obj["shader_asset"] = picojson::value{data.shader_asset}; } +bool RasterMaterialData::FromJson(const CustomAssetLibrary::AssetID& asset_id, + const picojson::object& json, RasterMaterialData* data) +{ + const auto parse_properties = [&](const char* name, + std::vector* properties) -> bool { + const auto properties_iter = json.find(name); + if (properties_iter == json.end()) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, '{}' not found", asset_id, name); + return false; + } + if (!properties_iter->second.is()) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, '{}' is not the right json type", + asset_id, name); + return false; + } + const auto& properties_array = properties_iter->second.get(); + + if (!ParseMaterialProperties(asset_id, properties_array, properties)) + return false; + return true; + }; + + if (!parse_properties("vertex_properties", &data->vertex_properties)) + return false; + + if (!parse_properties("pixel_properties", &data->pixel_properties)) + return false; + + if (const auto shader_asset = ReadStringFromJson(json, "shader_asset")) + { + data->shader_asset = *shader_asset; + } + else + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, 'shader_asset' not found or wrong type", + asset_id); + return false; + } + + if (const auto next_material_asset = ReadStringFromJson(json, "next_material_asset")) + { + data->next_material_asset = *next_material_asset; + } + else + { + ERROR_LOG_FMT(VIDEO, + "Asset '{}' failed to parse json, 'next_material_asset' not found or wrong type", + asset_id); + return false; + } + + if (const auto cull_mode = ReadNumericFromJson(json, "cull_mode")) + { + data->cull_mode = *cull_mode; + } + + if (const auto depth_state = json.find("depth_state"); + depth_state != json.end() && depth_state->second.is()) + { + auto& json_obj = depth_state->second.get(); + DepthState state; + state.testenable = ReadNumericFromJson(json_obj, "testenable").value_or(0); + state.updateenable = ReadNumericFromJson(json_obj, "updateenable").value_or(0); + state.func = ReadNumericFromJson(json_obj, "func").value_or(CompareMode::Never); + data->depth_state = state; + } + + if (const auto blending_state = json.find("blending_state"); + blending_state != json.end() && blending_state->second.is()) + { + auto& json_obj = blending_state->second.get(); + BlendingState state; + state.blendenable = ReadNumericFromJson(json_obj, "blendenable").value_or(0); + state.logicopenable = ReadNumericFromJson(json_obj, "logicopenable").value_or(0); + state.colorupdate = ReadNumericFromJson(json_obj, "colorupdate").value_or(0); + state.alphaupdate = ReadNumericFromJson(json_obj, "alphaupdate").value_or(0); + state.subtract = ReadNumericFromJson(json_obj, "subtract").value_or(0); + state.subtractAlpha = ReadNumericFromJson(json_obj, "subtractAlpha").value_or(0); + state.usedualsrc = ReadNumericFromJson(json_obj, "usedualsrc").value_or(0); + state.dstfactor = + ReadNumericFromJson(json_obj, "dstfactor").value_or(DstBlendFactor::Zero); + state.srcfactor = + ReadNumericFromJson(json_obj, "srcfactor").value_or(SrcBlendFactor::Zero); + state.dstfactoralpha = ReadNumericFromJson(json_obj, "dstfactoralpha") + .value_or(DstBlendFactor::Zero); + state.srcfactoralpha = ReadNumericFromJson(json_obj, "srcfactoralpha") + .value_or(SrcBlendFactor::Zero); + state.logicmode = ReadNumericFromJson(json_obj, "logicmode").value_or(LogicOp::Clear); + data->blending_state = state; + } + + const auto pixel_textures_iter = json.find("pixel_textures"); + if (pixel_textures_iter == json.end()) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, 'pixel_textures' not found", asset_id); + return false; + } + if (!pixel_textures_iter->second.is()) + { + ERROR_LOG_FMT(VIDEO, + "Asset '{}' failed to parse json, 'pixel_textures' is not the right json type", + asset_id); + return false; + } + const auto& pixel_textures_array = pixel_textures_iter->second.get(); + if (!std::ranges::all_of(pixel_textures_array, [](const picojson::value& json_data) { + return json_data.is(); + })) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, 'pixel_textures' must contain objects", + asset_id); + return false; + } + + for (const auto& pixel_texture_json : pixel_textures_array) + { + auto& pixel_texture_json_obj = pixel_texture_json.get(); + + TextureSamplerValue sampler_value; + if (!TextureSamplerValue::FromJson(pixel_texture_json_obj, &sampler_value)) + return false; + data->pixel_textures.push_back(std::move(sampler_value)); + } + + const auto render_targets_iter = json.find("render_targets"); + if (render_targets_iter == json.end()) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, 'render_targets' not found", asset_id); + return false; + } + if (!render_targets_iter->second.is()) + { + ERROR_LOG_FMT(VIDEO, + "Asset '{}' failed to parse json, 'render_targets' is not the right json type", + asset_id); + return false; + } + const auto& render_targets_array = render_targets_iter->second.get(); + if (!std::ranges::all_of(render_targets_array, [](const picojson::value& json_data) { + return json_data.is(); + })) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, 'render_targets' must contain strings", + asset_id); + return false; + } + + for (const auto& render_target_json : render_targets_array) + { + std::string render_target_str = render_target_json.to_str(); + data->render_targets.push_back(std::move(render_target_str)); + } + + return true; +} + +void RasterMaterialData::ToJson(picojson::object* obj, const RasterMaterialData& data) +{ + if (!obj) [[unlikely]] + return; + + auto& json_obj = *obj; + + const auto add_property = [](picojson::array* json_properties, + const MaterialProperty2& property) { + picojson::object json_property; + + std::visit(overloaded{[&](s32 value) { + json_property["type"] = picojson::value{"int"}; + json_property["value"] = picojson::value{static_cast(value)}; + }, + [&](const std::array& value) { + json_property["type"] = picojson::value{"int2"}; + json_property["value"] = picojson::value{ToJsonArray(value)}; + }, + [&](const std::array& value) { + json_property["type"] = picojson::value{"int3"}; + json_property["value"] = picojson::value{ToJsonArray(value)}; + }, + [&](const std::array& value) { + json_property["type"] = picojson::value{"int4"}; + json_property["value"] = picojson::value{ToJsonArray(value)}; + }, + [&](float value) { + json_property["type"] = picojson::value{"float"}; + json_property["value"] = picojson::value{static_cast(value)}; + }, + [&](const std::array& value) { + json_property["type"] = picojson::value{"float2"}; + json_property["value"] = picojson::value{ToJsonArray(value)}; + }, + [&](const std::array& value) { + json_property["type"] = picojson::value{"float3"}; + json_property["value"] = picojson::value{ToJsonArray(value)}; + }, + [&](const std::array& value) { + json_property["type"] = picojson::value{"float4"}; + json_property["value"] = picojson::value{ToJsonArray(value)}; + }, + [&](bool value) { + json_property["type"] = picojson::value{"bool"}; + json_property["value"] = picojson::value{value}; + }}, + property.m_value); + + json_properties->emplace_back(std::move(json_property)); + }; + + picojson::array json_vertex_properties; + for (const auto& property : data.vertex_properties) + { + add_property(&json_vertex_properties, property); + } + json_obj.emplace("vertex_properties", std::move(json_vertex_properties)); + + picojson::array json_pixel_properties; + for (const auto& property : data.pixel_properties) + { + add_property(&json_pixel_properties, property); + } + json_obj.emplace("pixel_properties", std::move(json_pixel_properties)); + + json_obj.emplace("shader_asset", data.shader_asset); + json_obj.emplace("next_material_asset", data.next_material_asset); + + if (data.cull_mode) + { + json_obj.emplace("cull_mode", static_cast(*data.cull_mode)); + } + + if (data.depth_state) + { + picojson::object depth_state_json; + depth_state_json.emplace("testenable", + static_cast(data.depth_state->testenable.Value())); + depth_state_json.emplace("updateenable", + static_cast(data.depth_state->updateenable.Value())); + depth_state_json.emplace("func", static_cast(data.depth_state->func.Value())); + json_obj.emplace("depth_state", depth_state_json); + } + + if (data.blending_state) + { + picojson::object blending_state_json; + blending_state_json.emplace("blendenable", + static_cast(data.blending_state->blendenable.Value())); + blending_state_json.emplace("logicopenable", + static_cast(data.blending_state->logicopenable.Value())); + blending_state_json.emplace("colorupdate", + static_cast(data.blending_state->colorupdate.Value())); + blending_state_json.emplace("alphaupdate", + static_cast(data.blending_state->alphaupdate.Value())); + blending_state_json.emplace("subtract", + static_cast(data.blending_state->subtract.Value())); + blending_state_json.emplace("subtractAlpha", + static_cast(data.blending_state->subtractAlpha.Value())); + blending_state_json.emplace("usedualsrc", + static_cast(data.blending_state->usedualsrc.Value())); + blending_state_json.emplace("dstfactor", + static_cast(data.blending_state->dstfactor.Value())); + blending_state_json.emplace("srcfactor", + static_cast(data.blending_state->srcfactor.Value())); + blending_state_json.emplace("dstfactoralpha", + static_cast(data.blending_state->dstfactoralpha.Value())); + blending_state_json.emplace("srcfactoralpha", + static_cast(data.blending_state->srcfactoralpha.Value())); + blending_state_json.emplace("logicmode", + static_cast(data.blending_state->logicmode.Value())); + json_obj.emplace("blending_state", blending_state_json); + } + + picojson::array json_textures; + for (const auto& texture : data.pixel_textures) + { + picojson::object json_texture; + TextureSamplerValue::ToJson(&json_texture, texture); + json_textures.emplace_back(std::move(json_texture)); + } + json_obj.emplace("pixel_textures", json_textures); + + picojson::array json_render_targets; + for (const auto& render_target : data.render_targets) + { + json_render_targets.emplace_back(render_target); + } + json_obj.emplace("render_targets", json_render_targets); +} + CustomAssetLibrary::LoadInfo MaterialAsset::LoadImpl(const CustomAssetLibrary::AssetID& asset_id) { auto potential_data = std::make_shared(); @@ -393,4 +890,19 @@ CustomAssetLibrary::LoadInfo MaterialAsset::LoadImpl(const CustomAssetLibrary::A } return loaded_info; } + +CustomAssetLibrary::LoadInfo +RasterMaterialAsset::LoadImpl(const CustomAssetLibrary::AssetID& asset_id) +{ + auto potential_data = std::make_shared(); + const auto loaded_info = m_owning_library->LoadMaterial(asset_id, potential_data.get()); + if (loaded_info.m_bytes_loaded == 0) + return {}; + { + std::lock_guard lk(m_data_lock); + m_loaded = true; + m_data = std::move(potential_data); + } + return loaded_info; +} } // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Assets/MaterialAsset.h b/Source/Core/VideoCommon/Assets/MaterialAsset.h index 0e9a4558a5..0c292d0fba 100644 --- a/Source/Core/VideoCommon/Assets/MaterialAsset.h +++ b/Source/Core/VideoCommon/Assets/MaterialAsset.h @@ -4,6 +4,7 @@ #pragma once #include +#include #include #include #include @@ -13,6 +14,9 @@ #include "Common/CommonTypes.h" #include "Common/EnumFormatter.h" #include "VideoCommon/Assets/CustomAsset.h" +#include "VideoCommon/Assets/TextureSamplerValue.h" +#include "VideoCommon/BPMemory.h" +#include "VideoCommon/RenderState.h" class ShaderCode; @@ -23,13 +27,23 @@ struct MaterialProperty static void WriteToMemory(u8*& buffer, const MaterialProperty& property); static std::size_t GetMemorySize(const MaterialProperty& property); static void WriteAsShaderCode(ShaderCode& shader_source, const MaterialProperty& property); - using Value = std::variant, - std::array, std::array, float, std::array, - std::array, std::array, bool>; + using Value = std::variant, std::array, + std::array, float, std::array, std::array, + std::array, bool>; std::string m_code_name; Value m_value; }; +struct MaterialProperty2 +{ + static void WriteToMemory(u8*& buffer, const MaterialProperty2& property); + static std::size_t GetMemorySize(const MaterialProperty2& property); + using Value = + std::variant, std::array, std::array, float, + std::array, std::array, std::array, bool>; + Value m_value; +}; + struct MaterialData { static bool FromJson(const CustomAssetLibrary::AssetID& asset_id, const picojson::object& json, @@ -52,4 +66,31 @@ private: CustomAssetLibrary::LoadInfo LoadImpl(const CustomAssetLibrary::AssetID& asset_id) override; }; +struct RasterMaterialData +{ + static bool FromJson(const CustomAssetLibrary::AssetID& asset_id, const picojson::object& json, + RasterMaterialData* data); + static void ToJson(picojson::object* obj, const RasterMaterialData& data); + CustomAssetLibrary::AssetID shader_asset; + CustomAssetLibrary::AssetID next_material_asset; + std::vector vertex_properties; + std::vector pixel_properties; + + std::optional cull_mode; + std::optional depth_state; + std::optional blending_state; + + std::vector pixel_textures; + std::vector render_targets; +}; + +class RasterMaterialAsset final : public CustomLoadableAsset +{ +public: + using CustomLoadableAsset::CustomLoadableAsset; + +private: + CustomAssetLibrary::LoadInfo LoadImpl(const CustomAssetLibrary::AssetID& asset_id) override; +}; + } // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Assets/MaterialAssetUtils.cpp b/Source/Core/VideoCommon/Assets/MaterialAssetUtils.cpp new file mode 100644 index 0000000000..741000d9cb --- /dev/null +++ b/Source/Core/VideoCommon/Assets/MaterialAssetUtils.cpp @@ -0,0 +1,200 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/Assets/MaterialAssetUtils.h" + +#include "Common/VariantUtil.h" + +#include "VideoCommon/Assets/MaterialAsset.h" +#include "VideoCommon/Assets/ShaderAsset.h" + +namespace VideoCommon +{ +void SetMaterialPropertiesFromShader(const VideoCommon::PixelShaderData& shader, + VideoCommon::MaterialData* material) +{ + material->properties.clear(); + for (const auto& entry : shader.m_properties) + { + // C++20: error with capturing structured bindings for our version of clang + const auto& name = entry.first; + const auto& shader_property = entry.second; + std::visit(overloaded{[&](const VideoCommon::ShaderProperty::Sampler2D& default_value) { + VideoCommon::MaterialProperty property; + property.m_code_name = name; + property.m_value = default_value.value; + material->properties.push_back(std::move(property)); + }, + [&](const VideoCommon::ShaderProperty::Sampler2DArray& default_value) { + VideoCommon::MaterialProperty property; + property.m_code_name = name; + property.m_value = default_value.value; + material->properties.push_back(std::move(property)); + }, + [&](const VideoCommon::ShaderProperty::SamplerCube& default_value) { + VideoCommon::MaterialProperty property; + property.m_code_name = name; + property.m_value = default_value.value; + material->properties.push_back(std::move(property)); + }, + [&](s32 default_value) { + VideoCommon::MaterialProperty property; + property.m_code_name = name; + property.m_value = default_value; + material->properties.push_back(std::move(property)); + }, + [&](const std::array& default_value) { + VideoCommon::MaterialProperty property; + property.m_code_name = name; + property.m_value = default_value; + material->properties.push_back(std::move(property)); + }, + [&](const std::array& default_value) { + VideoCommon::MaterialProperty property; + property.m_code_name = name; + property.m_value = default_value; + material->properties.push_back(std::move(property)); + }, + [&](const std::array& default_value) { + VideoCommon::MaterialProperty property; + property.m_code_name = name; + property.m_value = default_value; + material->properties.push_back(std::move(property)); + }, + [&](float default_value) { + VideoCommon::MaterialProperty property; + property.m_code_name = name; + property.m_value = default_value; + material->properties.push_back(std::move(property)); + }, + [&](const std::array& default_value) { + VideoCommon::MaterialProperty property; + property.m_code_name = name; + property.m_value = default_value; + material->properties.push_back(std::move(property)); + }, + [&](const std::array& default_value) { + VideoCommon::MaterialProperty property; + property.m_code_name = name; + property.m_value = default_value; + material->properties.push_back(std::move(property)); + }, + [&](const std::array& default_value) { + VideoCommon::MaterialProperty property; + property.m_code_name = name; + property.m_value = default_value; + material->properties.push_back(std::move(property)); + }, + [&](const VideoCommon::ShaderProperty::RGB& default_value) { + VideoCommon::MaterialProperty property; + property.m_code_name = name; + property.m_value = default_value.value; + material->properties.push_back(std::move(property)); + }, + [&](const VideoCommon::ShaderProperty::RGBA& default_value) { + VideoCommon::MaterialProperty property; + property.m_code_name = name; + property.m_value = default_value.value; + material->properties.push_back(std::move(property)); + }, + [&](bool default_value) { + VideoCommon::MaterialProperty property; + property.m_code_name = name; + property.m_value = default_value; + material->properties.push_back(std::move(property)); + }}, + shader_property.m_default); + } +} + +void SetMaterialPropertiesFromShader(const RasterShaderData& shader, RasterMaterialData* material) +{ + material->vertex_properties.clear(); + material->pixel_properties.clear(); + + const auto set_properties = [&](const std::map& shader_properties, + std::vector* material_properties) { + for (const auto& entry : shader_properties) + { + // C++20: error with capturing structured bindings for our version of clang + // const auto& name = entry.first; + const auto& shader_property = entry.second; + std::visit(overloaded{[&](s32 default_value) { + VideoCommon::MaterialProperty2 property; + property.m_value = default_value; + material_properties->push_back(std::move(property)); + }, + [&](const std::array& default_value) { + VideoCommon::MaterialProperty2 property; + property.m_value = default_value; + material_properties->push_back(std::move(property)); + }, + [&](const std::array& default_value) { + VideoCommon::MaterialProperty2 property; + property.m_value = default_value; + material_properties->push_back(std::move(property)); + }, + [&](const std::array& default_value) { + VideoCommon::MaterialProperty2 property; + property.m_value = default_value; + material_properties->push_back(std::move(property)); + }, + [&](float default_value) { + VideoCommon::MaterialProperty2 property; + property.m_value = default_value; + material_properties->push_back(std::move(property)); + }, + [&](const std::array& default_value) { + VideoCommon::MaterialProperty2 property; + property.m_value = default_value; + material_properties->push_back(std::move(property)); + }, + [&](const std::array& default_value) { + VideoCommon::MaterialProperty2 property; + property.m_value = default_value; + material_properties->push_back(std::move(property)); + }, + [&](const std::array& default_value) { + VideoCommon::MaterialProperty2 property; + property.m_value = default_value; + material_properties->push_back(std::move(property)); + }, + [&](const VideoCommon::ShaderProperty2::RGB& default_value) { + VideoCommon::MaterialProperty2 property; + property.m_value = default_value.value; + material_properties->push_back(std::move(property)); + }, + [&](const VideoCommon::ShaderProperty2::RGBA& default_value) { + VideoCommon::MaterialProperty2 property; + property.m_value = default_value.value; + material_properties->push_back(std::move(property)); + }, + [&](bool default_value) { + VideoCommon::MaterialProperty2 property; + property.m_value = default_value; + material_properties->push_back(std::move(property)); + }}, + shader_property.m_default); + } + }; + + set_properties(shader.m_vertex_properties, &material->vertex_properties); + set_properties(shader.m_pixel_properties, &material->pixel_properties); +} + +void SetMaterialTexturesFromShader(const RasterShaderData& shader, RasterMaterialData* material) +{ + material->pixel_textures.clear(); + + const auto set_textures = [&](const std::vector& shader_samplers, + std::vector* material_textures) { + for (std::size_t i = 0; i < shader_samplers.size(); i++) + { + TextureSamplerValue sampler_value; + material_textures->push_back(std::move(sampler_value)); + } + }; + + set_textures(shader.m_pixel_samplers, &material->pixel_textures); +} +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Assets/MaterialAssetUtils.h b/Source/Core/VideoCommon/Assets/MaterialAssetUtils.h new file mode 100644 index 0000000000..efff764525 --- /dev/null +++ b/Source/Core/VideoCommon/Assets/MaterialAssetUtils.h @@ -0,0 +1,19 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +struct MaterialData; +struct PixelShaderData; +struct RasterMaterialData; +struct RasterShaderData; + +namespace VideoCommon +{ +void SetMaterialPropertiesFromShader(const PixelShaderData& shader, MaterialData* material); +void SetMaterialPropertiesFromShader(const RasterShaderData& shader, RasterMaterialData* material); +void SetMaterialTexturesFromShader(const RasterShaderData& shader, RasterMaterialData* material); +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Assets/RenderTargetAsset.cpp b/Source/Core/VideoCommon/Assets/RenderTargetAsset.cpp new file mode 100644 index 0000000000..149af22ba3 --- /dev/null +++ b/Source/Core/VideoCommon/Assets/RenderTargetAsset.cpp @@ -0,0 +1,43 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/Assets/RenderTargetAsset.h" + +#include "Common/EnumUtils.h" +#include "Common/JsonUtil.h" +#include "Common/Logging/Log.h" + +namespace VideoCommon +{ +bool RenderTargetData::FromJson(const CustomAssetLibrary::AssetID& asset_id, + const picojson::object& json, RenderTargetData* data) +{ + data->m_texture_format = AbstractTextureFormat::RGBA8; + // TODO: parse format + return true; +} + +void RenderTargetData::ToJson(picojson::object* obj, const RenderTargetData& data) +{ + if (!obj) [[unlikely]] + return; + + auto& json_obj = *obj; + json_obj.emplace("format", static_cast(data.m_texture_format)); +} + +CustomAssetLibrary::LoadInfo +RenderTargetAsset::LoadImpl(const CustomAssetLibrary::AssetID& asset_id) +{ + auto potential_data = std::make_shared(); + const auto loaded_info = m_owning_library->LoadRenderTarget(asset_id, potential_data.get()); + if (loaded_info.m_bytes_loaded == 0) + return {}; + { + std::lock_guard lk(m_data_lock); + m_loaded = true; + m_data = std::move(potential_data); + } + return loaded_info; +} +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Assets/RenderTargetAsset.h b/Source/Core/VideoCommon/Assets/RenderTargetAsset.h new file mode 100644 index 0000000000..94a28e354b --- /dev/null +++ b/Source/Core/VideoCommon/Assets/RenderTargetAsset.h @@ -0,0 +1,31 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "VideoCommon/Assets/CustomAsset.h" +#include "VideoCommon/RenderState.h" +#include "VideoCommon/TextureConfig.h" + +namespace VideoCommon +{ +struct RenderTargetData +{ + static bool FromJson(const CustomAssetLibrary::AssetID& asset_id, const picojson::object& json, + RenderTargetData* data); + static void ToJson(picojson::object* obj, const RenderTargetData& data); + AbstractTextureFormat m_texture_format; + SamplerState m_sampler; +}; + +class RenderTargetAsset final : public CustomLoadableAsset +{ +public: + using CustomLoadableAsset::CustomLoadableAsset; + +private: + CustomAssetLibrary::LoadInfo LoadImpl(const CustomAssetLibrary::AssetID& asset_id) override; +}; +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Assets/ShaderAsset.cpp b/Source/Core/VideoCommon/Assets/ShaderAsset.cpp index dbe4c5cb02..8002f6572c 100644 --- a/Source/Core/VideoCommon/Assets/ShaderAsset.cpp +++ b/Source/Core/VideoCommon/Assets/ShaderAsset.cpp @@ -11,6 +11,8 @@ #include "Common/StringUtil.h" #include "Common/VariantUtil.h" #include "VideoCommon/Assets/CustomAssetLibrary.h" +#include "VideoCommon/Assets/TextureSamplerValue.h" +#include "VideoCommon/ShaderGenCommon.h" namespace VideoCommon { @@ -139,8 +141,17 @@ static bool ParseShaderValue(const CustomAssetLibrary::AssetID& asset_id, if (json_value.is()) { ShaderProperty::Sampler2D sampler2d; - sampler2d.value = json_value.get(); - *value = std::move(sampler2d); + sampler2d.value.asset = json_value.get(); + *value = sampler2d; + return true; + } + else if (json_value.is()) + { + ShaderProperty::Sampler2D sampler2d; + auto texture_asset_obj = json_value.get(); + if (!TextureSamplerValue::FromJson(texture_asset_obj, &sampler2d.value)) + return false; + *value = sampler2d; return true; } } @@ -149,8 +160,17 @@ static bool ParseShaderValue(const CustomAssetLibrary::AssetID& asset_id, if (json_value.is()) { ShaderProperty::Sampler2DArray sampler2darray; - sampler2darray.value = json_value.get(); - *value = std::move(sampler2darray); + sampler2darray.value.asset = json_value.get(); + *value = sampler2darray; + return true; + } + else if (json_value.is()) + { + ShaderProperty::Sampler2DArray sampler2darray; + auto texture_asset_obj = json_value.get(); + if (!TextureSamplerValue::FromJson(texture_asset_obj, &sampler2darray.value)) + return false; + *value = sampler2darray; return true; } } @@ -159,8 +179,83 @@ static bool ParseShaderValue(const CustomAssetLibrary::AssetID& asset_id, if (json_value.is()) { ShaderProperty::SamplerCube samplercube; - samplercube.value = json_value.get(); - *value = std::move(samplercube); + samplercube.value.asset = json_value.get(); + *value = samplercube; + return true; + } + else if (json_value.is()) + { + ShaderProperty::SamplerCube samplercube; + auto texture_asset_obj = json_value.get(); + if (!TextureSamplerValue::FromJson(texture_asset_obj, &samplercube.value)) + return false; + *value = samplercube; + return true; + } + } + + ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse the json, value is not valid for type '{}'", + asset_id, type); + return false; +} + +static bool ParseShaderValue(const CustomAssetLibrary::AssetID& asset_id, + const picojson::value& json_value, std::string_view code_name, + std::string_view type, ShaderProperty2::Value* value) +{ + if (type == "int") + { + return ParseNumeric(asset_id, json_value, code_name, value); + } + else if (type == "int2") + { + return ParseNumeric(asset_id, json_value, code_name, value); + } + else if (type == "int3") + { + return ParseNumeric(asset_id, json_value, code_name, value); + } + else if (type == "int4") + { + return ParseNumeric(asset_id, json_value, code_name, value); + } + else if (type == "float") + { + return ParseNumeric(asset_id, json_value, code_name, value); + } + else if (type == "float2") + { + return ParseNumeric(asset_id, json_value, code_name, value); + } + else if (type == "float3") + { + return ParseNumeric(asset_id, json_value, code_name, value); + } + else if (type == "float4") + { + return ParseNumeric(asset_id, json_value, code_name, value); + } + else if (type == "rgb") + { + ShaderProperty2::RGB rgb; + if (!ParseNumeric(asset_id, json_value, code_name, &rgb.value)) + return false; + *value = std::move(rgb); + return true; + } + else if (type == "rgba") + { + ShaderProperty2::RGBA rgba; + if (!ParseNumeric(asset_id, json_value, code_name, &rgba.value)) + return false; + *value = std::move(rgba); + return true; + } + else if (type == "bool") + { + if (json_value.is()) + { + *value = json_value.get(); return true; } } @@ -250,6 +345,10 @@ ParseShaderProperties(const VideoCommon::CustomAssetLibrary::AssetID& asset_id, return false; } } + else + { + property.m_default = ShaderProperty::GetDefaultValueFromTypeName(type); + } shader_properties->try_emplace(std::move(code_name), std::move(property)); } @@ -257,6 +356,260 @@ ParseShaderProperties(const VideoCommon::CustomAssetLibrary::AssetID& asset_id, return true; } +static bool +ParseShaderProperties(const VideoCommon::CustomAssetLibrary::AssetID& asset_id, + const picojson::array& properties_data, + std::map* shader_properties) +{ + if (!shader_properties) [[unlikely]] + return false; + + for (const auto& property_data : properties_data) + { + VideoCommon::ShaderProperty2 property; + if (!property_data.is()) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, property is not the right json type", + asset_id); + return false; + } + const auto& property_data_obj = property_data.get(); + + const auto type_iter = property_data_obj.find("type"); + if (type_iter == property_data_obj.end()) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, property entry 'type' not found", + asset_id); + return false; + } + if (!type_iter->second.is()) + { + ERROR_LOG_FMT(VIDEO, + "Asset '{}' failed to parse json, property entry 'type' is not " + "the right json type", + asset_id); + return false; + } + std::string type = type_iter->second.to_str(); + Common::ToLower(&type); + + const auto description_iter = property_data_obj.find("description"); + if (description_iter == property_data_obj.end()) + { + ERROR_LOG_FMT(VIDEO, + "Asset '{}' failed to parse json, property entry 'description' not found", + asset_id); + return false; + } + if (!description_iter->second.is()) + { + ERROR_LOG_FMT(VIDEO, + "Asset '{}' failed to parse json, property entry 'description' is not " + "the right json type", + asset_id); + return false; + } + property.m_description = description_iter->second.to_str(); + + const auto code_name_iter = property_data_obj.find("code_name"); + if (code_name_iter == property_data_obj.end()) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, property entry 'code_name' not found", + asset_id); + return false; + } + if (!code_name_iter->second.is()) + { + ERROR_LOG_FMT(VIDEO, + "Asset '{}' failed to parse json, property entry 'code_name' is not " + "the right json type", + asset_id); + return false; + } + std::string code_name = code_name_iter->second.to_str(); + + const auto default_iter = property_data_obj.find("default"); + if (default_iter != property_data_obj.end()) + { + if (!ParseShaderValue(asset_id, default_iter->second, code_name, type, &property.m_default)) + { + return false; + } + } + else + { + property.m_default = ShaderProperty2::GetDefaultValueFromTypeName(type); + } + + shader_properties->try_emplace(std::move(code_name), std::move(property)); + } + + return true; +} + +bool RasterShaderData::FromJson(const VideoCommon::CustomAssetLibrary::AssetID& asset_id, + const picojson::object& json, RasterShaderData* data) +{ + const auto parse_properties = [&](const char* name, std::string_view source, + std::map* properties) -> bool { + const auto properties_iter = json.find(name); + if (properties_iter == json.end()) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, '{}' not found", asset_id, name); + return false; + } + if (!properties_iter->second.is()) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, '{}' is not the right json type", + asset_id, name); + return false; + } + const auto& properties_array = properties_iter->second.get(); + + if (!ParseShaderProperties(asset_id, properties_array, properties)) + return false; + + for (const auto& [code_name, property] : *properties) + { + if (source.find(code_name) == std::string::npos) + { + ERROR_LOG_FMT( + VIDEO, + "Asset '{}' failed to parse json, the code name '{}' defined in the metadata was not " + "found in the source for '{}'", + asset_id, code_name, name); + return false; + } + } + + return true; + }; + + const auto parse_samplers = + [&](const char* name, + std::vector* samplers) -> bool { + const auto samplers_iter = json.find(name); + if (samplers_iter == json.end()) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, '{}' not found", asset_id, name); + return false; + } + if (!samplers_iter->second.is()) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, '{}' is not the right json type", + asset_id, name); + return false; + } + const auto& samplers_array = samplers_iter->second.get(); + if (!std::ranges::all_of(samplers_array, [](const picojson::value& json_data) { + return json_data.is(); + })) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, '{}' must contain objects", asset_id, + name); + return false; + } + + for (const auto& sampler_json : samplers_array) + { + auto& sampler_json_obj = sampler_json.get(); + + SamplerData sampler; + + if (const auto sampler_name = ReadStringFromJson(sampler_json_obj, "name")) + { + sampler.name = *sampler_name; + } + else + { + ERROR_LOG_FMT(VIDEO, + "Asset '{}' failed to parse sampler json, 'name' not found or wrong type", + asset_id); + return false; + } + + if (const auto sampler_type = + ReadNumericFromJson(sampler_json_obj, "type")) + { + sampler.type = *sampler_type; + } + else + { + ERROR_LOG_FMT(VIDEO, + "Asset '{}' failed to parse sampler json, 'type' not found or wrong type", + asset_id); + return false; + } + samplers->push_back(std::move(sampler)); + } + + return true; + }; + + const auto parse_output_targets = + [&](const char* name, + std::vector* output_targets) -> bool { + const auto output_targets_iter = json.find(name); + if (output_targets_iter == json.end()) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, '{}' not found", asset_id, name); + return false; + } + if (!output_targets_iter->second.is()) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, '{}' is not the right json type", + asset_id, name); + return false; + } + const auto& output_targets_array = output_targets_iter->second.get(); + if (!std::ranges::all_of(output_targets_array, [](const picojson::value& json_data) { + return json_data.is(); + })) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, '{}' must contain objects", asset_id, + name); + return false; + } + + for (const auto& output_target_json : output_targets_array) + { + auto& output_target_json_obj = output_target_json.get(); + + OutputTargetData output_target; + + if (const auto output_target_name = ReadStringFromJson(output_target_json_obj, "name")) + { + output_target.name = *output_target_name; + } + else + { + ERROR_LOG_FMT( + VIDEO, "Asset '{}' failed to parse output target json, 'name' not found or wrong type", + asset_id); + return false; + } + + output_targets->push_back(std::move(output_target)); + } + + return true; + }; + + if (!parse_properties("vertex_properties", data->m_vertex_source, &data->m_vertex_properties)) + return false; + + if (!parse_properties("pixel_properties", data->m_pixel_source, &data->m_pixel_properties)) + return false; + + if (!parse_samplers("pixel_samplers", &data->m_pixel_samplers)) + return false; + + if (!parse_output_targets("pixel_output_targets", &data->m_output_targets)) + return false; + + return true; +} + bool PixelShaderData::FromJson(const VideoCommon::CustomAssetLibrary::AssetID& asset_id, const picojson::object& json, PixelShaderData* data) { @@ -303,15 +656,21 @@ void PixelShaderData::ToJson(picojson::object& obj, const PixelShaderData& data) std::visit(overloaded{[&](const ShaderProperty::Sampler2D& default_value) { json_property.emplace("type", "sampler2d"); - json_property.emplace("default", default_value.value); + picojson::object texture_sampler_obj; + TextureSamplerValue::ToJson(&texture_sampler_obj, default_value.value); + json_property.emplace("default", picojson::value{texture_sampler_obj}); }, [&](const ShaderProperty::Sampler2DArray& default_value) { json_property.emplace("type", "sampler2darray"); - json_property.emplace("default", default_value.value); + picojson::object texture_sampler_obj; + TextureSamplerValue::ToJson(&texture_sampler_obj, default_value.value); + json_property.emplace("default", picojson::value{texture_sampler_obj}); }, [&](const ShaderProperty::SamplerCube& default_value) { json_property.emplace("type", "samplercube"); - json_property.emplace("default", default_value.value); + picojson::object texture_sampler_obj; + TextureSamplerValue::ToJson(&texture_sampler_obj, default_value.value); + json_property.emplace("default", picojson::value{texture_sampler_obj}); }, [&](s32 default_value) { json_property.emplace("type", "int"); @@ -365,6 +724,107 @@ void PixelShaderData::ToJson(picojson::object& obj, const PixelShaderData& data) obj.emplace("properties", std::move(json_properties)); } +void RasterShaderData::ToJson(picojson::object& obj, const RasterShaderData& data) +{ + const auto add_property = [](picojson::array* json_properties, const std::string& name, + const ShaderProperty2& property) { + picojson::object json_property; + + json_property.emplace("code_name", name); + json_property.emplace("description", property.m_description); + + std::visit(overloaded{[&](s32 default_value) { + json_property.emplace("type", "int"); + json_property.emplace("default", static_cast(default_value)); + }, + [&](const std::array& default_value) { + json_property.emplace("type", "int2"); + json_property.emplace("default", ToJsonArray(default_value)); + }, + [&](const std::array& default_value) { + json_property.emplace("type", "int3"); + json_property.emplace("default", ToJsonArray(default_value)); + }, + [&](const std::array& default_value) { + json_property.emplace("type", "int4"); + json_property.emplace("default", ToJsonArray(default_value)); + }, + [&](float default_value) { + json_property.emplace("type", "float"); + json_property.emplace("default", static_cast(default_value)); + }, + [&](const std::array& default_value) { + json_property.emplace("type", "float2"); + json_property.emplace("default", ToJsonArray(default_value)); + }, + [&](const std::array& default_value) { + json_property.emplace("type", "float3"); + json_property.emplace("default", ToJsonArray(default_value)); + }, + [&](const std::array& default_value) { + json_property.emplace("type", "float4"); + json_property.emplace("default", ToJsonArray(default_value)); + }, + [&](const ShaderProperty2::RGB& default_value) { + json_property.emplace("type", "rgb"); + json_property.emplace("default", ToJsonArray(default_value.value)); + }, + [&](const ShaderProperty2::RGBA& default_value) { + json_property.emplace("type", "rgba"); + json_property.emplace("default", ToJsonArray(default_value.value)); + }, + [&](bool default_value) { + json_property.emplace("type", "bool"); + json_property.emplace("default", default_value); + }}, + property.m_default); + + json_properties->emplace_back(std::move(json_property)); + }; + + const auto add_sampler = [](picojson::array* json_samplers, const SamplerData& sampler) { + picojson::object json_sampler; + json_sampler.emplace("name", sampler.name); + json_sampler.emplace("type", static_cast(sampler.type)); + json_samplers->emplace_back(std::move(json_sampler)); + }; + + const auto add_output_target = [](picojson::array* json_output_targets, + const OutputTargetData& output_target) { + picojson::object json_output_target; + json_output_target.emplace("name", output_target.name); + json_output_targets->emplace_back(std::move(json_output_target)); + }; + + picojson::array json_vertex_properties; + for (const auto& [name, property] : data.m_vertex_properties) + { + add_property(&json_vertex_properties, name, property); + } + obj.emplace("vertex_properties", std::move(json_vertex_properties)); + + picojson::array json_pixel_properties; + for (const auto& [name, property] : data.m_pixel_properties) + { + add_property(&json_pixel_properties, name, property); + } + obj.emplace("pixel_properties", std::move(json_pixel_properties)); + + picojson::array json_pixel_samplers; + for (const auto& sampler : data.m_pixel_samplers) + { + add_sampler(&json_pixel_samplers, sampler); + } + obj.emplace("pixel_samplers", json_pixel_samplers); + + picojson::array json_output_targets; + for (const auto& output_target : data.m_output_targets) + { + add_output_target(&json_output_targets, output_target); + } + obj.emplace("pixel_output_targets", json_output_targets); +} + std::span ShaderProperty::GetValueTypeNames() { static constexpr std::array values = { @@ -373,6 +833,13 @@ std::span ShaderProperty::GetValueTypeNames() return values; } +std::span ShaderProperty2::GetValueTypeNames() +{ + static constexpr std::array values = { + "int", "int2", "int3", "int4", "float", "float2", "float3", "float4", "rgb", "rgba", "bool"}; + return values; +} + ShaderProperty::Value ShaderProperty::GetDefaultValueFromTypeName(std::string_view name) { if (name == "sampler2d") @@ -435,6 +902,88 @@ ShaderProperty::Value ShaderProperty::GetDefaultValueFromTypeName(std::string_vi return Value{}; } +ShaderProperty2::Value ShaderProperty2::GetDefaultValueFromTypeName(std::string_view name) +{ + if (name == "int") + { + return 0; + } + else if (name == "int2") + { + return std::array{}; + } + else if (name == "int3") + { + return std::array{}; + } + else if (name == "int4") + { + return std::array{}; + } + else if (name == "float") + { + return 0.0f; + } + else if (name == "float2") + { + return std::array{}; + } + else if (name == "float3") + { + return std::array{}; + } + else if (name == "float4") + { + return std::array{}; + } + else if (name == "rgb") + { + return RGB{}; + } + else if (name == "rgba") + { + return RGBA{}; + } + else if (name == "bool") + { + return false; + } + + return Value{}; +} + +void ShaderProperty2::WriteAsShaderCode(ShaderCode& shader_source, std::string_view name, + const ShaderProperty2& property) +{ + const auto write_shader = [&](std::string_view type, u32 element_count) { + if (element_count == 1) + { + shader_source.Write("{} {};\n", type, name); + } + else + { + shader_source.Write("{}{} {};\n", type, element_count, name); + } + + for (std::size_t i = element_count; i < 4; i++) + { + shader_source.Write("{} {}_padding_{};\n", type, name, i + 1); + } + }; + std::visit(overloaded{[&](s32) { write_shader("int", 1); }, + [&](const std::array&) { write_shader("int", 2); }, + [&](const std::array&) { write_shader("int", 3); }, + [&](const std::array&) { write_shader("int", 4); }, + [&](float) { write_shader("float", 1); }, + [&](const std::array&) { write_shader("float", 2); }, + [&](const std::array&) { write_shader("float", 3); }, + [&](const std::array&) { write_shader("float", 4); }, + [&](const ShaderProperty2::RGB&) { write_shader("float", 3); }, + [&](const ShaderProperty2::RGBA&) { write_shader("float", 4); }, + [&](bool) { write_shader("bool", 1); }}, + property.m_default); +} + CustomAssetLibrary::LoadInfo PixelShaderAsset::LoadImpl(const CustomAssetLibrary::AssetID& asset_id) { auto potential_data = std::make_shared(); @@ -448,4 +997,19 @@ CustomAssetLibrary::LoadInfo PixelShaderAsset::LoadImpl(const CustomAssetLibrary } return loaded_info; } + +CustomAssetLibrary::LoadInfo +RasterShaderAsset::LoadImpl(const CustomAssetLibrary::AssetID& asset_id) +{ + auto potential_data = std::make_shared(); + const auto loaded_info = m_owning_library->LoadShader(asset_id, potential_data.get()); + if (loaded_info.m_bytes_loaded == 0) + return {}; + { + std::lock_guard lk(m_data_lock); + m_loaded = true; + m_data = std::move(potential_data); + } + return loaded_info; +} } // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Assets/ShaderAsset.h b/Source/Core/VideoCommon/Assets/ShaderAsset.h index 6a9c100d44..fa296f1028 100644 --- a/Source/Core/VideoCommon/Assets/ShaderAsset.h +++ b/Source/Core/VideoCommon/Assets/ShaderAsset.h @@ -13,6 +13,10 @@ #include #include "VideoCommon/Assets/CustomAsset.h" +#include "VideoCommon/Assets/TextureSamplerValue.h" +#include "VideoCommon/TextureConfig.h" + +class ShaderCode; namespace VideoCommon { @@ -30,17 +34,17 @@ struct ShaderProperty struct Sampler2D { - CustomAssetLibrary::AssetID value; + TextureSamplerValue value; }; struct Sampler2DArray { - CustomAssetLibrary::AssetID value; + TextureSamplerValue value; }; struct SamplerCube { - CustomAssetLibrary::AssetID value; + TextureSamplerValue value; }; using Value = std::variant, std::array, std::array, float, @@ -52,6 +56,31 @@ struct ShaderProperty Value m_default; std::string m_description; }; + +struct ShaderProperty2 +{ + struct RGB + { + std::array value; + }; + + struct RGBA + { + std::array value; + }; + + using Value = std::variant, std::array, std::array, float, + std::array, std::array, std::array, bool, + RGB, RGBA>; + static std::span GetValueTypeNames(); + static Value GetDefaultValueFromTypeName(std::string_view name); + static void WriteAsShaderCode(ShaderCode& shader_source, std::string_view name, + const ShaderProperty2& property); + + Value m_default; + std::string m_description; +}; + struct PixelShaderData { static bool FromJson(const CustomAssetLibrary::AssetID& asset_id, const picojson::object& json, @@ -71,6 +100,49 @@ class PixelShaderAsset final : public CustomLoadableAsset public: using CustomLoadableAsset::CustomLoadableAsset; +private: + CustomAssetLibrary::LoadInfo LoadImpl(const CustomAssetLibrary::AssetID& asset_id) override; +}; + +struct RasterShaderData +{ + static bool FromJson(const CustomAssetLibrary::AssetID& asset_id, const picojson::object& json, + RasterShaderData* data); + static void ToJson(picojson::object& obj, const RasterShaderData& data); + + // These shader properties describe the input that the + // shader expects to expose. The key is text + // expected to be in the shader code and the propery + // describes various details about the input + std::map m_vertex_properties; + std::string m_vertex_source; + + std::map m_pixel_properties; + std::string m_pixel_source; + + struct SamplerData + { + AbstractTextureType type; + std::string name; + + bool operator==(const SamplerData&) const = default; + }; + std::vector m_pixel_samplers; + + struct OutputTargetData + { + std::string name; + + bool operator==(const OutputTargetData&) const = default; + }; + std::vector m_output_targets; +}; + +class RasterShaderAsset final : public CustomLoadableAsset +{ +public: + using CustomLoadableAsset::CustomLoadableAsset; + private: CustomAssetLibrary::LoadInfo LoadImpl(const CustomAssetLibrary::AssetID& asset_id) override; }; diff --git a/Source/Core/VideoCommon/Assets/TextureSamplerValue.cpp b/Source/Core/VideoCommon/Assets/TextureSamplerValue.cpp new file mode 100644 index 0000000000..e059e252ed --- /dev/null +++ b/Source/Core/VideoCommon/Assets/TextureSamplerValue.cpp @@ -0,0 +1,77 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/Assets/TextureSamplerValue.h" + +#include + +#include "Common/EnumUtils.h" +#include "Common/JsonUtil.h" +#include "Common/StringUtil.h" + +namespace VideoCommon +{ +namespace +{ +std::optional +ReadSamplerOriginFromJSON(const picojson::object& json) +{ + auto sampler_origin = ReadStringFromJson(json, "sampler_origin").value_or(""); + Common::ToLower(&sampler_origin); + + if (sampler_origin == "asset") + { + return TextureSamplerValue::SamplerOrigin::Asset; + } + else if (sampler_origin == "texture_hash") + { + return TextureSamplerValue::SamplerOrigin::TextureHash; + } + + return std::nullopt; +} +} // namespace +std::string TextureSamplerValue::ToString(SamplerOrigin sampler_origin) +{ + if (sampler_origin == SamplerOrigin::Asset) + return "asset"; + + return "texture_hash"; +} + +bool TextureSamplerValue::FromJson(const picojson::object& json, TextureSamplerValue* data) +{ + data->asset = ReadStringFromJson(json, "asset").value_or(""); + data->texture_hash = ReadStringFromJson(json, "texture_hash").value_or(""); + data->sampler_origin = + ReadSamplerOriginFromJSON(json).value_or(TextureSamplerValue::SamplerOrigin::Asset); + + // Render targets + data->is_render_target = ReadBoolFromJson(json, "is_render_target").value_or(false); + data->camera_originating_draw_call = + GraphicsModSystem::DrawCallID{ReadNumericFromJson(json, "camera_draw_call").value_or(0)}; + data->camera_type = static_cast( + ReadNumericFromJson(json, "camera_type").value_or(0)); + + return true; +} + +void TextureSamplerValue::ToJson(picojson::object* obj, const TextureSamplerValue& data) +{ + if (!obj) [[unlikely]] + return; + + obj->emplace("asset", data.asset); + obj->emplace("texture_hash", data.texture_hash); + obj->emplace("sampler_origin", ToString(data.sampler_origin)); + obj->emplace("is_render_target", data.is_render_target); + + const auto camera_draw_call = static_cast( + Common::ToUnderlying(data.camera_originating_draw_call)); + obj->emplace("camera_draw_call", camera_draw_call); + + const auto camera_type = + static_cast(Common::ToUnderlying(data.camera_type)); + obj->emplace("camera_type", camera_type); +} +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Assets/TextureSamplerValue.h b/Source/Core/VideoCommon/Assets/TextureSamplerValue.h new file mode 100644 index 0000000000..79967755b3 --- /dev/null +++ b/Source/Core/VideoCommon/Assets/TextureSamplerValue.h @@ -0,0 +1,35 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include + +#include "VideoCommon/Assets/CustomAssetLibrary.h" +#include "VideoCommon/GraphicsModSystem/Types.h" + +namespace VideoCommon +{ +struct TextureSamplerValue +{ + CustomAssetLibrary::AssetID asset; + + enum class SamplerOrigin + { + Asset, + TextureHash + }; + static std::string ToString(SamplerOrigin sampler_origin); + SamplerOrigin sampler_origin = SamplerOrigin::Asset; + std::string texture_hash; + + bool is_render_target = false; + GraphicsModSystem::CameraType camera_type; + GraphicsModSystem::DrawCallID camera_originating_draw_call; + + static bool FromJson(const picojson::object& json, TextureSamplerValue* data); + static void ToJson(picojson::object* obj, const TextureSamplerValue& data); +}; +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Assets/Types.h b/Source/Core/VideoCommon/Assets/Types.h new file mode 100644 index 0000000000..c10d45587c --- /dev/null +++ b/Source/Core/VideoCommon/Assets/Types.h @@ -0,0 +1,13 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +namespace VideoCommon::Assets +{ +using AssetMap = std::map; +} diff --git a/Source/Core/VideoCommon/Assets/WatchableFilesystemAssetLibrary.cpp b/Source/Core/VideoCommon/Assets/WatchableFilesystemAssetLibrary.cpp new file mode 100644 index 0000000000..4808463b4a --- /dev/null +++ b/Source/Core/VideoCommon/Assets/WatchableFilesystemAssetLibrary.cpp @@ -0,0 +1,57 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/Assets/WatchableFilesystemAssetLibrary.h" + +#include "Common/Logging/Log.h" +#include "Common/StringUtil.h" + +namespace VideoCommon +{ +void WatchableFilesystemAssetLibrary::Watch(const std::string& path) +{ + const auto [iter, inserted] = m_watched_paths.try_emplace(path, nullptr); + if (inserted) + { + iter->second = std::make_unique(path, [this](wtr::event e) { + if (e.path_type == wtr::event::path_type::watcher) + { + return; + } + + if (e.effect_type == wtr::event::effect_type::create) + { + const auto path = WithUnifiedPathSeparators(PathToString(e.path_name)); + PathAdded(path); + } + else if (e.effect_type == wtr::event::effect_type::modify) + { + const auto path = WithUnifiedPathSeparators(PathToString(e.path_name)); + PathModified(path); + } + else if (e.effect_type == wtr::event::effect_type::rename) + { + if (!e.associated) + { + WARN_LOG_FMT(VIDEO, "Rename on path seen without association!"); + return; + } + + const auto old_path = WithUnifiedPathSeparators(PathToString(e.path_name)); + const auto new_path = WithUnifiedPathSeparators(PathToString(e.associated->path_name)); + PathRenamed(old_path, new_path); + } + else if (e.effect_type == wtr::event::effect_type::destroy) + { + const auto path = WithUnifiedPathSeparators(PathToString(e.path_name)); + PathDeleted(path); + } + }); + } +} + +void WatchableFilesystemAssetLibrary::Unwatch(const std::string& path) +{ + m_watched_paths.erase(path); +} +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Assets/WatchableFilesystemAssetLibrary.h b/Source/Core/VideoCommon/Assets/WatchableFilesystemAssetLibrary.h new file mode 100644 index 0000000000..947127a469 --- /dev/null +++ b/Source/Core/VideoCommon/Assets/WatchableFilesystemAssetLibrary.h @@ -0,0 +1,38 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include + +#include + +#include "VideoCommon/Assets/CustomAssetLibrary.h" + +namespace VideoCommon +{ +class WatchableFilesystemAssetLibrary : public CustomAssetLibrary +{ +public: + void Watch(const std::string& path); + void Unwatch(const std::string& path); + +private: + // A new file or folder was added to one of the watched paths + virtual void PathAdded(std::string_view path) {} + + // A file or folder was modified in one of the watched paths + virtual void PathModified(std::string_view path) {} + + // A file or folder was renamed in one of the watched paths + virtual void PathRenamed(std::string_view old_path, std::string_view new_path) {} + + // A file or folder was deleted in one of the watched paths + virtual void PathDeleted(std::string_view path) {} + + std::map> m_watched_paths; +}; +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/CMakeLists.txt b/Source/Core/VideoCommon/CMakeLists.txt index 6361e639c4..8f602f9e59 100644 --- a/Source/Core/VideoCommon/CMakeLists.txt +++ b/Source/Core/VideoCommon/CMakeLists.txt @@ -14,18 +14,29 @@ add_library(videocommon Assets/CustomAssetLibrary.h Assets/CustomAssetLoader.cpp Assets/CustomAssetLoader.h + Assets/CustomAssetLoader2.cpp + Assets/CustomAssetLoader2.h Assets/CustomTextureData.cpp Assets/CustomTextureData.h Assets/DirectFilesystemAssetLibrary.cpp Assets/DirectFilesystemAssetLibrary.h Assets/MaterialAsset.cpp Assets/MaterialAsset.h + Assets/MaterialAssetUtils.cpp + Assets/MaterialAssetUtils.h Assets/MeshAsset.cpp Assets/MeshAsset.h + Assets/RenderTargetAsset.cpp + Assets/RenderTargetAsset.h Assets/ShaderAsset.cpp Assets/ShaderAsset.h Assets/TextureAsset.cpp Assets/TextureAsset.h + Assets/TextureSamplerValue.cpp + Assets/TextureSamplerValue.h + Assets/Types.h + Assets/WatchableFilesystemAssetLibrary.cpp + Assets/WatchableFilesystemAssetLibrary.h AsyncRequests.cpp AsyncRequests.h AsyncShaderCompiler.cpp @@ -66,41 +77,113 @@ add_library(videocommon GeometryShaderGen.h GeometryShaderManager.cpp GeometryShaderManager.h + GraphicsModEditor/Controls/AssetDisplay.cpp + GraphicsModEditor/Controls/AssetDisplay.h + GraphicsModEditor/Controls/CameraChoiceControl.cpp + GraphicsModEditor/Controls/CameraChoiceControl.h + GraphicsModEditor/Controls/MaterialControl.cpp + GraphicsModEditor/Controls/MaterialControl.h + GraphicsModEditor/Controls/MaterialGenerateWindow.cpp + GraphicsModEditor/Controls/MaterialGenerateWindow.h + GraphicsModEditor/Controls/MeshControl.cpp + GraphicsModEditor/Controls/MeshControl.h + GraphicsModEditor/Controls/MeshExtractWindow.cpp + GraphicsModEditor/Controls/MeshExtractWindow.h + GraphicsModEditor/Controls/MeshImportWindow.cpp + GraphicsModEditor/Controls/MeshImportWindow.h + GraphicsModEditor/Controls/MiscControls.cpp + GraphicsModEditor/Controls/MiscControls.h + GraphicsModEditor/Controls/RenderTargetControl.cpp + GraphicsModEditor/Controls/RenderTargetControl.h + GraphicsModEditor/Controls/ShaderControl.cpp + GraphicsModEditor/Controls/ShaderControl.h + GraphicsModEditor/Controls/TagSelectionWindow.cpp + GraphicsModEditor/Controls/TagSelectionWindow.h + GraphicsModEditor/Controls/TextureControl.cpp + GraphicsModEditor/Controls/TextureControl.h + GraphicsModEditor/EditorAssetSource.cpp + GraphicsModEditor/EditorAssetSource.h + GraphicsModEditor/EditorBackend.cpp + GraphicsModEditor/EditorBackend.h + GraphicsModEditor/EditorEvents.h + GraphicsModEditor/EditorFilter.cpp + GraphicsModEditor/EditorFilter.h + GraphicsModEditor/EditorMain.cpp + GraphicsModEditor/EditorMain.h + GraphicsModEditor/EditorState.cpp + GraphicsModEditor/EditorState.h + GraphicsModEditor/EditorTypes.h + GraphicsModEditor/MaterialGeneration.cpp + GraphicsModEditor/MaterialGeneration.h + GraphicsModEditor/Panels/ActiveTargetsPanel.cpp + GraphicsModEditor/Panels/ActiveTargetsPanel.h + GraphicsModEditor/Panels/AssetBrowserPanel.cpp + GraphicsModEditor/Panels/AssetBrowserPanel.h + GraphicsModEditor/Panels/PropertiesPanel.cpp + GraphicsModEditor/Panels/PropertiesPanel.h + GraphicsModEditor/RenderTargetGeneration.cpp + GraphicsModEditor/RenderTargetGeneration.h + GraphicsModEditor/SceneDumper.cpp + GraphicsModEditor/SceneDumper.h + GraphicsModEditor/SceneUtils.cpp + GraphicsModEditor/SceneUtils.h + GraphicsModEditor/ShaderGeneration.cpp + GraphicsModEditor/ShaderGeneration.h GraphicsModSystem/Config/GraphicsMod.cpp GraphicsModSystem/Config/GraphicsMod.h + GraphicsModSystem/Config/GraphicsModAction.cpp + GraphicsModSystem/Config/GraphicsModAction.h GraphicsModSystem/Config/GraphicsModAsset.cpp GraphicsModSystem/Config/GraphicsModAsset.h - GraphicsModSystem/Config/GraphicsModFeature.cpp - GraphicsModSystem/Config/GraphicsModFeature.h GraphicsModSystem/Config/GraphicsModGroup.cpp GraphicsModSystem/Config/GraphicsModGroup.h + GraphicsModSystem/Config/GraphicsModHashPolicy.cpp + GraphicsModSystem/Config/GraphicsModHashPolicy.h + GraphicsModSystem/Config/GraphicsModTag.cpp + GraphicsModSystem/Config/GraphicsModTag.h GraphicsModSystem/Config/GraphicsTarget.cpp GraphicsModSystem/Config/GraphicsTarget.h - GraphicsModSystem/Config/GraphicsTargetGroup.cpp - GraphicsModSystem/Config/GraphicsTargetGroup.h GraphicsModSystem/Constants.h + GraphicsModSystem/Runtime/Actions/CustomMeshAction.cpp + GraphicsModSystem/Runtime/Actions/CustomMeshAction.h GraphicsModSystem/Runtime/Actions/CustomPipelineAction.cpp GraphicsModSystem/Runtime/Actions/CustomPipelineAction.h + GraphicsModSystem/Runtime/Actions/ModifyLight.cpp + GraphicsModSystem/Runtime/Actions/ModifyLight.h GraphicsModSystem/Runtime/Actions/MoveAction.cpp GraphicsModSystem/Runtime/Actions/MoveAction.h GraphicsModSystem/Runtime/Actions/PrintAction.cpp GraphicsModSystem/Runtime/Actions/PrintAction.h + GraphicsModSystem/Runtime/Actions/RelativeCameraAction.cpp + GraphicsModSystem/Runtime/Actions/RelativeCameraAction.h GraphicsModSystem/Runtime/Actions/ScaleAction.cpp GraphicsModSystem/Runtime/Actions/ScaleAction.h GraphicsModSystem/Runtime/Actions/SkipAction.cpp GraphicsModSystem/Runtime/Actions/SkipAction.h - GraphicsModSystem/Runtime/CustomPipeline.cpp - GraphicsModSystem/Runtime/CustomPipeline.h - GraphicsModSystem/Runtime/CustomShaderCache.cpp - GraphicsModSystem/Runtime/CustomShaderCache.h + GraphicsModSystem/Runtime/Actions/TransformAction.cpp + GraphicsModSystem/Runtime/Actions/TransformAction.h + GraphicsModSystem/Runtime/CameraManager.cpp + GraphicsModSystem/Runtime/CameraManager.h + GraphicsModSystem/Runtime/CustomResourceManager.cpp + GraphicsModSystem/Runtime/CustomResourceManager.h + GraphicsModSystem/Runtime/CustomShaderCache2.cpp + GraphicsModSystem/Runtime/CustomShaderCache2.h + GraphicsModSystem/Runtime/CustomTextureCache2.cpp + GraphicsModSystem/Runtime/CustomTextureCache2.h GraphicsModSystem/Runtime/FBInfo.cpp GraphicsModSystem/Runtime/FBInfo.h GraphicsModSystem/Runtime/GraphicsModAction.h GraphicsModSystem/Runtime/GraphicsModActionData.h GraphicsModSystem/Runtime/GraphicsModActionFactory.cpp GraphicsModSystem/Runtime/GraphicsModActionFactory.h + GraphicsModSystem/Runtime/GraphicsModBackend.cpp + GraphicsModSystem/Runtime/GraphicsModBackend.h + GraphicsModSystem/Runtime/GraphicsModHash.cpp + GraphicsModSystem/Runtime/GraphicsModHash.h GraphicsModSystem/Runtime/GraphicsModManager.cpp GraphicsModSystem/Runtime/GraphicsModManager.h + GraphicsModSystem/Runtime/GraphicsModRuntimeBackend.cpp + GraphicsModSystem/Runtime/GraphicsModRuntimeBackend.h HiresTextures.cpp HiresTextures.h IndexGenerator.cpp @@ -219,6 +302,7 @@ PRIVATE implot glslang tinygltf + watcher ) if(_M_X86_64) diff --git a/Source/Core/VideoCommon/ConstantManager.h b/Source/Core/VideoCommon/ConstantManager.h index 14c8732580..ab399f3a4e 100644 --- a/Source/Core/VideoCommon/ConstantManager.h +++ b/Source/Core/VideoCommon/ConstantManager.h @@ -103,6 +103,8 @@ struct alignas(16) VertexShaderConstants u32 vertex_offset_posmtx; std::array vertex_offset_colors; std::array vertex_offset_texcoords; + // For custom meshes (TODO: move) + std::array custom_transform; }; enum class VSExpand : u32 diff --git a/Source/Core/VideoCommon/GraphicsModEditor/Controls/AssetDisplay.cpp b/Source/Core/VideoCommon/GraphicsModEditor/Controls/AssetDisplay.cpp new file mode 100644 index 0000000000..77e50fc3f2 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/Controls/AssetDisplay.cpp @@ -0,0 +1,267 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModEditor/Controls/AssetDisplay.h" + +#include "VideoCommon/GraphicsModEditor/EditorEvents.h" + +#include +#include +#include + +#include + +namespace GraphicsModEditor::Controls +{ +namespace +{ +std::string_view AssetDragDropTypeFromType(AssetDataType asset_type) +{ + switch (asset_type) + { + case Material: + return "MaterialAsset"; + case RasterMaterial: + return "RasterMaterialAsset"; + case PixelShader: + return "PixelShaderAsset"; + case Shader: + return "ShaderAsset"; + case Texture: + return "TextureAsset"; + case Mesh: + return "MeshAsset"; + } + + return ""; +} + +AbstractTexture* GenericImageIconFromType(AssetDataType asset_type, const EditorState& state) +{ + switch (asset_type) + { + case Material: + if (const auto it = state.m_editor_data.m_name_to_texture.find("file"); + it != state.m_editor_data.m_name_to_texture.end()) + { + return it->second.get(); + } + case RasterMaterial: + if (const auto it = state.m_editor_data.m_name_to_texture.find("file"); + it != state.m_editor_data.m_name_to_texture.end()) + { + return it->second.get(); + } + case PixelShader: + if (const auto it = state.m_editor_data.m_name_to_texture.find("code"); + it != state.m_editor_data.m_name_to_texture.end()) + { + return it->second.get(); + } + case Shader: + if (const auto it = state.m_editor_data.m_name_to_texture.find("code"); + it != state.m_editor_data.m_name_to_texture.end()) + { + return it->second.get(); + } + case Texture: + if (const auto it = state.m_editor_data.m_name_to_texture.find("image"); + it != state.m_editor_data.m_name_to_texture.end()) + { + return it->second.get(); + } + case Mesh: + if (const auto it = state.m_editor_data.m_name_to_texture.find("file"); + it != state.m_editor_data.m_name_to_texture.end()) + { + return it->second.get(); + } + } + + return nullptr; +} + +ImVec2 asset_button_size{150, 150}; +} // namespace +bool AssetDisplay(std::string_view popup_name, EditorState* state, + VideoCommon::CustomAssetLibrary::AssetID* asset_id_chosen, + AssetDataType* asset_type_chosen, + std::span asset_types_allowed) +{ + if (!state) [[unlikely]] + return false; + if (!asset_id_chosen) [[unlikely]] + return false; + if (!asset_type_chosen) [[unlikely]] + return false; + + static std::string asset_filter_text = ""; + + const std::string reset_popup_name = std::string{popup_name} + "Reset"; + bool changed = false; + const EditorAsset* asset = nullptr; + if (!asset_id_chosen->empty()) + { + asset = state->m_user_data.m_asset_library->GetAssetFromID(*asset_id_chosen); + } + if (!asset) + { + if (ImGui::Button("None", asset_button_size)) + { + if (!ImGui::IsPopupOpen(popup_name.data())) + { + asset_filter_text = ""; + ImGui::OpenPopup(popup_name.data()); + } + } + } + else + { + AbstractTexture* texture = + state->m_user_data.m_asset_library->GetAssetPreview(asset->m_asset_id); + state->m_editor_data.m_assets_waiting_for_preview.erase(asset->m_asset_id); + ImGui::BeginGroup(); + if (texture) + { + if (ImGui::ImageButton(asset->m_asset_id.c_str(), *texture, asset_button_size)) + { + if (!ImGui::IsPopupOpen(popup_name.data())) + { + asset_filter_text = ""; + ImGui::OpenPopup(popup_name.data()); + } + } + if (ImGui::BeginPopupContextItem(reset_popup_name.data())) + { + if (ImGui::Selectable("View properties")) + { + EditorEvents::JumpToAssetInBrowserEvent::Trigger(asset->m_asset_id); + } + if (ImGui::Selectable("Reset")) + { + *asset_id_chosen = ""; + changed = true; + } + ImGui::EndPopup(); + } + ImGui::TextWrapped("%s", PathToString(asset->m_asset_path.stem()).c_str()); + } + else + { + if (ImGui::Button(PathToString(asset->m_asset_path).c_str(), asset_button_size)) + { + if (!ImGui::IsPopupOpen(popup_name.data())) + { + asset_filter_text = ""; + ImGui::OpenPopup(popup_name.data()); + } + } + } + ImGui::EndGroup(); + } + if (ImGui::BeginDragDropTarget()) + { + for (const auto asset_type_allowed : asset_types_allowed) + { + if (const ImGuiPayload* payload = + ImGui::AcceptDragDropPayload(AssetDragDropTypeFromType(asset_type_allowed).data())) + { + VideoCommon::CustomAssetLibrary::AssetID new_asset_id( + static_cast(payload->Data), payload->DataSize); + *asset_id_chosen = new_asset_id; + *asset_type_chosen = asset_type_allowed; + changed = true; + } + } + ImGui::EndDragDropTarget(); + } + + // Asset browser popup below + const ImVec2 center = ImGui::GetMainViewport()->GetCenter(); + const ImVec2 size = ImGui::GetMainViewport()->WorkSize; + ImGui::SetNextWindowPos(center, ImGuiCond_Always, ImVec2(0.5f, 0.5f)); + ImGui::SetNextWindowSize(ImVec2(size.x / 4.0f, size.y / 2.0f)); + if (ImGui::BeginPopup(popup_name.data())) + { + const u32 column_count = 5; + u32 current_columns = 0; + u32 assets_displayed = 0; + + const float search_size = 200.0f; + ImGui::SetNextItemWidth(search_size); + ImGui::InputTextWithHint("##", "Search...", &asset_filter_text); + + if (ImGui::BeginTable("AssetBrowserPopupTable", column_count)) + { + ImGui::TableNextRow(); + for (const auto& asset_from_library : state->m_user_data.m_asset_library->GetAllAssets()) + { + if (std::ranges::find(asset_types_allowed, asset_from_library.m_data_type) == + asset_types_allowed.end()) + { + continue; + } + + const auto name = PathToString(asset_from_library.m_asset_path.stem()); + if (!asset_filter_text.empty() && name.find(asset_filter_text) == std::string::npos) + { + continue; + } + + assets_displayed++; + ImGui::TableNextColumn(); + ImGui::BeginGroup(); + + AbstractTexture* texture = + state->m_user_data.m_asset_library->GetAssetPreview(asset_from_library.m_asset_id); + if (texture) + { + if (ImGui::ImageButton(asset_from_library.m_asset_id.c_str(), *texture, + asset_button_size)) + { + *asset_id_chosen = asset_from_library.m_asset_id; + changed = true; + ImGui::CloseCurrentPopup(); + } + ImGui::TextWrapped("%s", name.c_str()); + } + else + { + if (ImGui::Button(name.c_str(), asset_button_size)) + { + *asset_id_chosen = asset_from_library.m_asset_id; + changed = true; + ImGui::CloseCurrentPopup(); + } + } + ImGui::EndGroup(); + + current_columns++; + if (current_columns == column_count) + { + ImGui::TableNextRow(); + current_columns = 0; + } + } + ImGui::EndTable(); + } + + if (assets_displayed == 0) + { + ImGui::Text("No assets found"); + } + ImGui::EndPopup(); + } + + return changed; +} + +bool AssetDisplay(std::string_view popup_name, EditorState* state, + VideoCommon::CustomAssetLibrary::AssetID* asset_id_chosen, + AssetDataType asset_type_allowed) +{ + AssetDataType asset_data_type_chosen; + return AssetDisplay(popup_name, state, asset_id_chosen, &asset_data_type_chosen, + std::array{asset_type_allowed}); +} +} // namespace GraphicsModEditor::Controls diff --git a/Source/Core/VideoCommon/GraphicsModEditor/Controls/AssetDisplay.h b/Source/Core/VideoCommon/GraphicsModEditor/Controls/AssetDisplay.h new file mode 100644 index 0000000000..98cf963590 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/Controls/AssetDisplay.h @@ -0,0 +1,24 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "VideoCommon/Assets/CustomAssetLibrary.h" +#include "VideoCommon/GraphicsModEditor/EditorState.h" +#include "VideoCommon/GraphicsModEditor/EditorTypes.h" + +namespace GraphicsModEditor::Controls +{ +// Displays an asset that can be overwritten by dragging/dropping or selecting from an asset browser +// Returns true if asset id changed +bool AssetDisplay(std::string_view popup_name, EditorState* state, + VideoCommon::CustomAssetLibrary::AssetID* asset_id_chosen, + AssetDataType* asset_type_chosen, + std::span asset_types_allowed); +bool AssetDisplay(std::string_view popup_name, EditorState* state, + VideoCommon::CustomAssetLibrary::AssetID* asset_id_chosen, + AssetDataType asset_type_allowed); +} // namespace GraphicsModEditor::Controls diff --git a/Source/Core/VideoCommon/GraphicsModEditor/Controls/CameraChoiceControl.cpp b/Source/Core/VideoCommon/GraphicsModEditor/Controls/CameraChoiceControl.cpp new file mode 100644 index 0000000000..ca3d920229 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/Controls/CameraChoiceControl.cpp @@ -0,0 +1,117 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModEditor/Controls/CameraChoiceControl.h" + +#include + +#include + +#include "Core/System.h" + +#include "VideoCommon/GraphicsModEditor/EditorBackend.h" +#include "VideoCommon/GraphicsModEditor/SceneUtils.h" +#include "VideoCommon/GraphicsModSystem/Runtime/Actions/RelativeCameraAction.h" +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h" + +namespace GraphicsModEditor::Controls +{ +namespace +{ +ImVec2 camera_button_size{150, 150}; + +std::string GetCameraName(const EditorState& editor_state, GraphicsModSystem::DrawCallID draw_call) +{ + std::string camera_name = ""; + const auto actions = GetActionsForDrawCall(editor_state, draw_call); + if (!actions.empty()) + { + const std::string draw_call_name = GetDrawCallName(editor_state, draw_call); + camera_name = fmt::format("{}/{}", draw_call_name, GetActionName(actions[0])); + } + return camera_name; +} +} // namespace +bool CameraChoiceControl(std::string_view popup_name, EditorState* editor_state, + GraphicsModSystem::DrawCallID* draw_call_chosen) +{ + if (!editor_state) [[unlikely]] + return false; + if (!draw_call_chosen) [[unlikely]] + return false; + + const std::string camera_name = GetCameraName(*editor_state, *draw_call_chosen); + if (camera_name.empty()) + ImGui::Text("Camera: None"); + else + ImGui::Text("Camera: %s", camera_name.c_str()); + + if (ImGui::Button("Pick camera")) + { + ImGui::OpenPopup(popup_name.data()); + } + bool changed = false; + + // Camera popup below + const ImVec2 center = ImGui::GetMainViewport()->GetCenter(); + const ImVec2 size = ImGui::GetMainViewport()->WorkSize; + ImGui::SetNextWindowPos(center, ImGuiCond_Always, ImVec2(0.5f, 0.5f)); + ImGui::SetNextWindowSize(ImVec2(size.x / 4.0f, size.y / 2.0f)); + if (ImGui::BeginPopup(popup_name.data())) + { + const u32 column_count = 5; + u32 current_columns = 0; + u32 cameras_displayed = 0; + + const float search_size = 200.0f; + ImGui::SetNextItemWidth(search_size); + + std::string camera_filter = ""; + ImGui::InputTextWithHint("##", "Search...", &camera_filter); + + if (ImGui::BeginTable("CameraPopupTable", column_count)) + { + ImGui::TableNextRow(); + + auto& manager = Core::System::GetInstance().GetGraphicsModManager(); + auto& backend = static_cast(manager.GetBackend()); + const auto camera_ids = backend.GetCameraManager().GetDrawCallsWithCameras(); + for (const auto& draw_call : camera_ids) + { + const std::string camera_name_in_popup = GetCameraName(*editor_state, draw_call); + if (camera_name_in_popup.empty()) + continue; + if (!camera_filter.empty() && camera_name_in_popup.find(camera_filter) == std::string::npos) + { + continue; + } + + cameras_displayed++; + ImGui::TableNextColumn(); + if (ImGui::Button(camera_name_in_popup.c_str())) + { + *draw_call_chosen = draw_call; + changed = true; + ImGui::CloseCurrentPopup(); + } + + current_columns++; + if (current_columns == column_count) + { + ImGui::TableNextRow(); + current_columns = 0; + } + } + ImGui::EndTable(); + } + + if (cameras_displayed == 0) + { + ImGui::Text("No cameras found"); + } + ImGui::EndPopup(); + } + + return changed; +} +} // namespace GraphicsModEditor::Controls diff --git a/Source/Core/VideoCommon/GraphicsModEditor/Controls/CameraChoiceControl.h b/Source/Core/VideoCommon/GraphicsModEditor/Controls/CameraChoiceControl.h new file mode 100644 index 0000000000..78d28c664a --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/Controls/CameraChoiceControl.h @@ -0,0 +1,15 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "VideoCommon/GraphicsModEditor/EditorState.h" +#include "VideoCommon/GraphicsModSystem/Types.h" + +namespace GraphicsModEditor::Controls +{ +bool CameraChoiceControl(std::string_view popup_name, EditorState* editor_state, + GraphicsModSystem::DrawCallID* draw_call_chosen); +} // namespace GraphicsModEditor::Controls diff --git a/Source/Core/VideoCommon/GraphicsModEditor/Controls/MaterialControl.cpp b/Source/Core/VideoCommon/GraphicsModEditor/Controls/MaterialControl.cpp new file mode 100644 index 0000000000..8d960613b0 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/Controls/MaterialControl.cpp @@ -0,0 +1,831 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModEditor/Controls/MaterialControl.h" + +#include +#include + +#include +#include +#include + +#include "Common/EnumUtils.h" +#include "Common/VariantUtil.h" + +#include "VideoCommon/Assets/MaterialAsset.h" +#include "VideoCommon/Assets/MaterialAssetUtils.h" +#include "VideoCommon/BPMemory.h" +#include "VideoCommon/GraphicsModEditor/Controls/AssetDisplay.h" +#include "VideoCommon/GraphicsModEditor/Controls/CameraChoiceControl.h" +#include "VideoCommon/GraphicsModEditor/EditorAssetSource.h" +#include "VideoCommon/GraphicsModEditor/EditorEvents.h" +#include "VideoCommon/RenderState.h" + +namespace GraphicsModEditor::Controls +{ +MaterialControl::MaterialControl(EditorState& state) : m_state(state) +{ +} + +void MaterialControl::DrawImGui(const VideoCommon::CustomAssetLibrary::AssetID& asset_id, + VideoCommon::MaterialData* material, + VideoCommon::CustomAssetLibrary::TimeType* last_data_write, + bool* valid) +{ + if (ImGui::BeginTable("MaterialShaderForm", 2)) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("ID"); + ImGui::TableNextColumn(); + ImGui::Text("%s", asset_id.c_str()); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Shader"); + ImGui::TableNextColumn(); + + if (AssetDisplay("MaterialShaderAsset", &m_state, &material->shader_asset, + AssetDataType::PixelShader)) + { + auto asset = m_state.m_user_data.m_asset_library->GetAssetFromID(material->shader_asset); + if (!asset) + { + ImGui::Text("Please choose a shader for this material"); + material->shader_asset = ""; + *valid = false; + return; + } + else + { + auto shader = std::get_if>(&asset->m_data); + if (!shader) + { + ImGui::Text( + "%s", + fmt::format("Asset id '{}' was not type shader!", material->shader_asset).c_str()); + material->shader_asset = ""; + *valid = false; + return; + } + SetMaterialPropertiesFromShader(*shader->get(), material); + *last_data_write = std::chrono::system_clock::now(); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + *valid = true; + } + } + ImGui::EndTable(); + } + + if (!*valid) + return; + + // Look up shader + auto asset = m_state.m_user_data.m_asset_library->GetAssetFromID(material->shader_asset); + if (!asset) + { + ImGui::Text("Please choose a shader for this material"); + } + else + { + auto shader = std::get_if>(&asset->m_data); + if (!shader) + { + ImGui::Text( + "%s", fmt::format("Asset id '{}' was not type shader!", material->shader_asset).c_str()); + } + else + { + VideoCommon::PixelShaderData* shader_data = shader->get(); + if (!asset->m_valid) + { + ImGui::Text("%s", + fmt::format("The shader '{}' is invalid!", material->shader_asset).c_str()); + } + else + { + DrawControl(asset_id, shader_data, material, last_data_write); + } + } + } +} + +void MaterialControl::DrawImGui(const VideoCommon::CustomAssetLibrary::AssetID& asset_id, + VideoCommon::RasterMaterialData* material, bool* valid) +{ + if (ImGui::BeginTable("MaterialShaderForm", 2)) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("ID"); + ImGui::TableNextColumn(); + ImGui::Text("%s", asset_id.c_str()); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Shader"); + ImGui::TableNextColumn(); + + if (AssetDisplay("MaterialShaderAsset", &m_state, &material->shader_asset, + AssetDataType::Shader)) + { + auto asset = m_state.m_user_data.m_asset_library->GetAssetFromID(material->shader_asset); + if (!asset) + { + ImGui::Text("Please choose a shader for this material"); + material->shader_asset = ""; + *valid = false; + return; + } + else + { + auto shader = std::get_if>(&asset->m_data); + if (!shader) + { + ImGui::Text( + "%s", + fmt::format("Asset id '{}' was not type shader!", material->shader_asset).c_str()); + material->shader_asset = ""; + *valid = false; + return; + } + SetMaterialPropertiesFromShader(*shader->get(), material); + SetMaterialTexturesFromShader(*shader->get(), material); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + *valid = true; + } + } + ImGui::EndTable(); + } + + if (!*valid) + return; + + // Look up shader + auto asset = m_state.m_user_data.m_asset_library->GetAssetFromID(material->shader_asset); + if (!asset) + { + ImGui::Text("Please choose a shader for this material"); + } + else + { + auto shader = std::get_if>(&asset->m_data); + if (!shader) + { + ImGui::Text( + "%s", fmt::format("Asset id '{}' was not type shader!", material->shader_asset).c_str()); + } + else + { + VideoCommon::RasterShaderData* shader_data = shader->get(); + if (!asset->m_valid) + { + ImGui::Text("%s", + fmt::format("The shader '{}' is invalid!", material->shader_asset).c_str()); + } + else + { + DrawControl(asset_id, shader_data, material); + } + } + } +} + +void MaterialControl::DrawSamplers(const VideoCommon::CustomAssetLibrary::AssetID& asset_id, + EditorState* editor_state, + std::span samplers, + std::span textures) +{ + std::size_t texture_sampler_index = 0; + for (const auto& texture_sampler : samplers) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%s", texture_sampler.name.c_str()); + ImGui::TableNextColumn(); + + auto& texture = textures[texture_sampler_index]; + + constexpr std::array asset_types_allowed{ + AssetDataType::Texture, AssetDataType::RenderTarget}; + AssetDataType asset_type_chosen = + texture.is_render_target ? AssetDataType::RenderTarget : AssetDataType::Texture; + if (AssetDisplay(texture_sampler.name, editor_state, &texture.asset, &asset_type_chosen, + asset_types_allowed)) + { + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + texture.is_render_target = asset_type_chosen == AssetDataType::RenderTarget; + + if (texture.is_render_target) + { + // Camera type TODO + if (texture.camera_type == GraphicsModSystem::CameraType::Specify) + { + if (CameraChoiceControl("Camera Choice", editor_state, + &texture.camera_originating_draw_call)) + { + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + } + } + + const auto sampler_origin_str = + VideoCommon::TextureSamplerValue::ToString(texture.sampler_origin); + if (ImGui::BeginCombo(fmt::format("##{}SamplerOrigin", texture_sampler.name).c_str(), + sampler_origin_str.c_str())) + { + static constexpr std::array + all_sampler_types = {VideoCommon::TextureSamplerValue::SamplerOrigin::Asset, + VideoCommon::TextureSamplerValue::SamplerOrigin::TextureHash}; + + for (const auto& type : all_sampler_types) + { + const bool is_selected = type == texture.sampler_origin; + const auto type_name = VideoCommon::TextureSamplerValue::ToString(type); + if (ImGui::Selectable(fmt::format("{}##{}", type_name, texture_sampler.name).c_str(), + is_selected)) + { + texture.sampler_origin = type; + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + } + ImGui::EndCombo(); + } + + if (texture.sampler_origin == VideoCommon::TextureSamplerValue::SamplerOrigin::Asset) + { + ImGui::BeginDisabled(); + } + if (ImGui::InputText(fmt::format("##{}TextureHash", texture_sampler.name).c_str(), + &texture.texture_hash)) + { + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + if (texture.sampler_origin == VideoCommon::TextureSamplerValue::SamplerOrigin::Asset) + { + ImGui::EndDisabled(); + } + + texture_sampler_index++; + } +} + +void MaterialControl::DrawRenderTargets( + const VideoCommon::CustomAssetLibrary::AssetID& asset_id, EditorState* editor_state, + std::span output_targets, + std::vector* render_targets) +{ + if (!render_targets) [[unlikely]] + return; + + constexpr std::array asset_types_allowed{ + AssetDataType::RenderTarget}; + AssetDataType asset_type_chosen = AssetDataType::RenderTarget; + for (std::size_t i = 0; i < output_targets.size(); i++) + { + auto& output_target = output_targets[i]; + auto& render_target = (*render_targets)[i]; + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%s", output_target.name.c_str()); + ImGui::TableNextColumn(); + + if (AssetDisplay("RenderTargetWindow", editor_state, &render_target, &asset_type_chosen, + asset_types_allowed)) + { + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + } +} + +void MaterialControl::DrawUniforms( + const VideoCommon::CustomAssetLibrary::AssetID& asset_id, EditorState*, + const std::map& shader_properties, + std::vector* material_properties) +{ + std::size_t material_property_index = 0; + for (const auto& entry : shader_properties) + { + // C++20: error with capturing structured bindings for our version of clang + const auto& name = entry.first; + const auto& shader_property = entry.second; + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%s", name.c_str()); + ImGui::TableNextColumn(); + + auto& material_property = (*material_properties)[material_property_index]; + + std::visit( + overloaded{[&](s32& value) { + if (ImGui::InputInt(fmt::format("##{}", name).c_str(), &value)) + { + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + }, + [&](std::array& value) { + if (ImGui::InputInt2(fmt::format("##{}", name).c_str(), value.data())) + { + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + }, + [&](std::array& value) { + if (ImGui::InputInt3(fmt::format("##{}", name).c_str(), value.data())) + { + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + }, + [&](std::array& value) { + if (ImGui::InputInt4(fmt::format("##{}", name).c_str(), value.data())) + { + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + }, + [&](float& value) { + if (ImGui::InputFloat(fmt::format("##{}", name).c_str(), &value)) + { + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + }, + [&](std::array& value) { + if (ImGui::InputFloat2(fmt::format("##{}", name).c_str(), value.data())) + { + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + }, + [&](std::array& value) { + if (std::holds_alternative( + shader_property.m_default)) + { + if (ImGui::ColorEdit3(fmt::format("##{}", name).c_str(), value.data())) + { + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + } + else + { + if (ImGui::InputFloat3(fmt::format("##{}", name).c_str(), value.data())) + { + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + } + }, + [&](std::array& value) { + if (std::holds_alternative( + shader_property.m_default)) + { + if (ImGui::ColorEdit4(fmt::format("##{}", name).c_str(), value.data())) + { + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + } + else + { + if (ImGui::InputFloat4(fmt::format("##{}", name).c_str(), value.data())) + { + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + } + }, + [&](bool& value) { + if (ImGui::Checkbox(fmt::format("##{}", name).c_str(), &value)) + { + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + }}, + material_property.m_value); + material_property_index++; + } +} + +void MaterialControl::DrawProperties(const VideoCommon::CustomAssetLibrary::AssetID& asset_id, + EditorState*, VideoCommon::RasterMaterialData* material) +{ + if (ImGui::CollapsingHeader("Blending", ImGuiTreeNodeFlags_DefaultOpen)) + { + bool custom_blending = material->blending_state.has_value(); + if (ImGui::Checkbox("Use Custom Data", &custom_blending)) + { + if (custom_blending && !material->blending_state) + { + material->blending_state = BlendingState{}; + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + else if (!custom_blending && material->blending_state) + { + material->blending_state.reset(); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + } + + if (material->blending_state && ImGui::BeginTable("BlendingForm", 2)) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Blend Enable"); + ImGui::TableNextColumn(); + + bool blend_enable = material->blending_state->blendenable == 0; + if (ImGui::Checkbox("##BlendEnable", &blend_enable)) + { + material->blending_state->blendenable = static_cast(blend_enable); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Logicop Enable"); + ImGui::TableNextColumn(); + + bool logicop_enable = material->blending_state->logicopenable == 0; + if (ImGui::Checkbox("##LogicOpEnable", &logicop_enable)) + { + material->blending_state->logicopenable = static_cast(logicop_enable); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Color Update"); + ImGui::TableNextColumn(); + + bool colorupdate = material->blending_state->colorupdate == 0; + if (ImGui::Checkbox("##ColorUpdate", &colorupdate)) + { + material->blending_state->colorupdate = static_cast(colorupdate); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Alpha Update"); + ImGui::TableNextColumn(); + + bool alphaupdate = material->blending_state->alphaupdate == 0; + if (ImGui::Checkbox("##AlphaUpdate", &alphaupdate)) + { + material->blending_state->alphaupdate = static_cast(alphaupdate); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + + ImGui::EndTable(); + } + } + + if (ImGui::CollapsingHeader("Culling", ImGuiTreeNodeFlags_DefaultOpen)) + { + bool custom_cull_mode = material->cull_mode.has_value(); + if (ImGui::Checkbox("Use Custom Data##2", &custom_cull_mode)) + { + if (custom_cull_mode && !material->cull_mode) + { + material->cull_mode = CullMode{}; + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + else if (!custom_cull_mode && material->cull_mode) + { + material->cull_mode.reset(); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + } + + if (material->cull_mode && ImGui::BeginTable("CullingForm", 2)) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Cull Mode"); + ImGui::TableNextColumn(); + + if (ImGui::BeginCombo("##CullMode", fmt::to_string(*material->cull_mode).c_str())) + { + static constexpr std::array all_cull_modes = {CullMode::None, CullMode::Back, + CullMode::Front, CullMode::All}; + + for (const auto& cull_mode : all_cull_modes) + { + const bool is_selected = cull_mode == *material->cull_mode; + const auto cull_mode_str = fmt::to_string(cull_mode); + if (ImGui::Selectable(fmt::format("{}##", cull_mode_str).c_str(), is_selected)) + { + material->cull_mode = cull_mode; + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + } + ImGui::EndCombo(); + } + + ImGui::EndTable(); + } + } + + if (ImGui::CollapsingHeader("Depth Test", ImGuiTreeNodeFlags_DefaultOpen)) + { + bool custom_depth = material->depth_state.has_value(); + if (ImGui::Checkbox("Use Custom Data##3", &custom_depth)) + { + if (custom_depth && !material->depth_state) + { + material->depth_state = DepthState{}; + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + else if (!custom_depth && material->depth_state) + { + material->depth_state.reset(); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + } + + if (material->depth_state && ImGui::BeginTable("DepthForm", 2)) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Test Enable"); + ImGui::TableNextColumn(); + + bool test_enable = static_cast(material->depth_state->testenable); + if (ImGui::Checkbox("##TestEnable", &test_enable)) + { + material->depth_state->testenable = static_cast(test_enable); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Update Enable"); + ImGui::TableNextColumn(); + + bool update_enable = static_cast(material->depth_state->updateenable); + if (ImGui::Checkbox("##UpdateEnable", &update_enable)) + { + material->depth_state->updateenable = static_cast(update_enable); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Comparison"); + ImGui::TableNextColumn(); + + if (ImGui::BeginCombo("##CompareMode", fmt::to_string(material->depth_state->func).c_str())) + { + static constexpr std::array all_compare_modes = { + CompareMode::Never, CompareMode::Less, CompareMode::Equal, CompareMode::LEqual, + CompareMode::Greater, CompareMode::NEqual, CompareMode::GEqual, CompareMode::Always}; + + for (const auto& compare_mode : all_compare_modes) + { + const bool is_selected = compare_mode == material->depth_state->func; + const auto compare_mode_str = fmt::to_string(compare_mode); + if (ImGui::Selectable(fmt::format("{}##", compare_mode_str).c_str(), is_selected)) + { + material->depth_state->func = compare_mode; + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + } + ImGui::EndCombo(); + } + + ImGui::EndTable(); + } + } +} + +void MaterialControl::DrawLinkedMaterial(const VideoCommon::CustomAssetLibrary::AssetID& asset_id, + EditorState* editor_state, + VideoCommon::RasterMaterialData* material) +{ + if (AssetDisplay("Next Material", editor_state, &material->next_material_asset, + AssetDataType::RasterMaterial)) + { + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } +} + +void MaterialControl::DrawControl(const VideoCommon::CustomAssetLibrary::AssetID& asset_id, + VideoCommon::PixelShaderData* shader, + VideoCommon::MaterialData* material, + VideoCommon::CustomAssetLibrary::TimeType* last_data_write) +{ + if (ImGui::CollapsingHeader("Properties", ImGuiTreeNodeFlags_DefaultOpen)) + { + if (ImGui::BeginTable("MaterialPropertiesForm", 2)) + { + std::size_t material_property_index = 0; + for (const auto& entry : shader->m_properties) + { + // C++20: error with capturing structured bindings for our version of clang + const auto& name = entry.first; + const auto& shader_property = entry.second; + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%s", name.c_str()); + ImGui::TableNextColumn(); + + auto& material_property = material->properties[material_property_index]; + + std::visit( + overloaded{ + [&](VideoCommon::TextureSamplerValue& value) { + if (AssetDisplay(material_property.m_code_name, &m_state, &value.asset, + AssetDataType::Texture)) + { + *last_data_write = std::chrono::system_clock::now(); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + + const auto sampler_origin_str = + VideoCommon::TextureSamplerValue::ToString(value.sampler_origin); + if (ImGui::BeginCombo( + fmt::format("##{}SamplerOrigin", material_property.m_code_name).c_str(), + sampler_origin_str.c_str())) + { + static std::array + all_sampler_types = { + VideoCommon::TextureSamplerValue::SamplerOrigin::Asset, + VideoCommon::TextureSamplerValue::SamplerOrigin::TextureHash}; + + for (const auto& type : all_sampler_types) + { + const bool is_selected = type == value.sampler_origin; + const auto type_name = VideoCommon::TextureSamplerValue::ToString(type); + if (ImGui::Selectable( + fmt::format("{}##{}", type_name, material_property.m_code_name) + .c_str(), + is_selected)) + { + value.sampler_origin = type; + *last_data_write = std::chrono::system_clock::now(); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + } + ImGui::EndCombo(); + } + + if (value.sampler_origin == + VideoCommon::TextureSamplerValue::SamplerOrigin::Asset) + { + ImGui::BeginDisabled(); + } + if (ImGui::InputText( + fmt::format("##{}TextureHash", material_property.m_code_name).c_str(), + &value.texture_hash)) + { + *last_data_write = std::chrono::system_clock::now(); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + if (value.sampler_origin == + VideoCommon::TextureSamplerValue::SamplerOrigin::Asset) + { + ImGui::EndDisabled(); + } + }, + [&](s32& value) { + if (ImGui::InputInt(fmt::format("##{}", material_property.m_code_name).c_str(), + &value)) + { + *last_data_write = std::chrono::system_clock::now(); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + }, + [&](std::array& value) { + if (ImGui::InputInt2(fmt::format("##{}", material_property.m_code_name).c_str(), + value.data())) + { + *last_data_write = std::chrono::system_clock::now(); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + }, + [&](std::array& value) { + if (ImGui::InputInt3(fmt::format("##{}", material_property.m_code_name).c_str(), + value.data())) + { + *last_data_write = std::chrono::system_clock::now(); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + }, + [&](std::array& value) { + if (ImGui::InputInt4(fmt::format("##{}", material_property.m_code_name).c_str(), + value.data())) + { + *last_data_write = std::chrono::system_clock::now(); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + }, + [&](float& value) { + if (ImGui::InputFloat(fmt::format("##{}", material_property.m_code_name).c_str(), + &value)) + { + *last_data_write = std::chrono::system_clock::now(); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + }, + [&](std::array& value) { + if (ImGui::InputFloat2(fmt::format("##{}", material_property.m_code_name).c_str(), + value.data())) + { + *last_data_write = std::chrono::system_clock::now(); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + }, + [&](std::array& value) { + if (std::holds_alternative( + shader_property.m_default)) + { + if (ImGui::ColorEdit3( + fmt::format("##{}", material_property.m_code_name).c_str(), + value.data())) + { + *last_data_write = std::chrono::system_clock::now(); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + } + else + { + if (ImGui::InputFloat3( + fmt::format("##{}", material_property.m_code_name).c_str(), + value.data())) + { + *last_data_write = std::chrono::system_clock::now(); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + } + }, + [&](std::array& value) { + if (std::holds_alternative( + shader_property.m_default)) + { + if (ImGui::ColorEdit4( + fmt::format("##{}", material_property.m_code_name).c_str(), + value.data())) + { + *last_data_write = std::chrono::system_clock::now(); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + } + else + { + if (ImGui::InputFloat4( + fmt::format("##{}", material_property.m_code_name).c_str(), + value.data())) + { + *last_data_write = std::chrono::system_clock::now(); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + } + }, + [&](bool& value) { + if (ImGui::Checkbox(fmt::format("##{}", material_property.m_code_name).c_str(), + &value)) + { + *last_data_write = std::chrono::system_clock::now(); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + }}, + material_property.m_value); + material_property_index++; + } + ImGui::EndTable(); + } + } +} + +void MaterialControl::DrawControl(const VideoCommon::CustomAssetLibrary::AssetID& asset_id, + VideoCommon::RasterShaderData* shader, + VideoCommon::RasterMaterialData* material) +{ + if (ImGui::CollapsingHeader("Input", ImGuiTreeNodeFlags_DefaultOpen)) + { + if (ImGui::BeginTable("MaterialVertexPropertiesForm", 2)) + { + DrawUniforms(asset_id, &m_state, shader->m_vertex_properties, &material->vertex_properties); + + ImGui::EndTable(); + } + + if (ImGui::BeginTable("MaterialPixelPropertiesForm", 2)) + { + DrawUniforms(asset_id, &m_state, shader->m_pixel_properties, &material->pixel_properties); + DrawSamplers(asset_id, &m_state, shader->m_pixel_samplers, material->pixel_textures); + + ImGui::EndTable(); + } + } + + if (ImGui::CollapsingHeader("Output", ImGuiTreeNodeFlags_DefaultOpen)) + { + if (ImGui::BeginTable("MaterialRenderTargetsForm", 2)) + { + DrawRenderTargets(asset_id, &m_state, shader->m_output_targets, &material->render_targets); + ImGui::EndTable(); + } + } + + DrawProperties(asset_id, &m_state, material); + DrawLinkedMaterial(asset_id, &m_state, material); +} +} // namespace GraphicsModEditor::Controls diff --git a/Source/Core/VideoCommon/GraphicsModEditor/Controls/MaterialControl.h b/Source/Core/VideoCommon/GraphicsModEditor/Controls/MaterialControl.h new file mode 100644 index 0000000000..7bd914d21e --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/Controls/MaterialControl.h @@ -0,0 +1,65 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#include "VideoCommon/Assets/CustomAsset.h" +#include "VideoCommon/Assets/ShaderAsset.h" +#include "VideoCommon/GraphicsModEditor/EditorState.h" + +namespace VideoCommon +{ +struct MaterialData; +struct PixelShaderData; +struct RasterMaterialData; +} // namespace VideoCommon + +namespace GraphicsModEditor::Controls +{ +class MaterialControl +{ +public: + explicit MaterialControl(EditorState& state); + void DrawImGui(const VideoCommon::CustomAssetLibrary::AssetID& asset_id, + VideoCommon::MaterialData* material, + VideoCommon::CustomAssetLibrary::TimeType* last_data_write, bool* valid); + void DrawImGui(const VideoCommon::CustomAssetLibrary::AssetID& asset_id, + VideoCommon::RasterMaterialData* material, bool* valid); + + static void DrawSamplers(const VideoCommon::CustomAssetLibrary::AssetID& asset_id, + EditorState* editor_state, + std::span samplers, + std::span textures); + + static void + DrawRenderTargets(const VideoCommon::CustomAssetLibrary::AssetID& asset_id, + EditorState* editor_state, + std::span output_targets, + std::vector* render_targets); + + static void + DrawUniforms(const VideoCommon::CustomAssetLibrary::AssetID& asset_id, EditorState* editor_state, + const std::map& shader_properties, + std::vector* material_properties); + + static void DrawProperties(const VideoCommon::CustomAssetLibrary::AssetID& asset_id, + EditorState* editor_state, VideoCommon::RasterMaterialData* material); + + static void DrawLinkedMaterial(const VideoCommon::CustomAssetLibrary::AssetID& asset_id, + EditorState* editor_state, + VideoCommon::RasterMaterialData* material); + +private: + void DrawControl(const VideoCommon::CustomAssetLibrary::AssetID& asset_id, + VideoCommon::PixelShaderData* shader, VideoCommon::MaterialData* material, + VideoCommon::CustomAssetLibrary::TimeType* last_data_write); + void DrawControl(const VideoCommon::CustomAssetLibrary::AssetID& asset_id, + VideoCommon::RasterShaderData* shader, + VideoCommon::RasterMaterialData* material); + EditorState& m_state; +}; +} // namespace GraphicsModEditor::Controls diff --git a/Source/Core/VideoCommon/GraphicsModEditor/Controls/MaterialGenerateWindow.cpp b/Source/Core/VideoCommon/GraphicsModEditor/Controls/MaterialGenerateWindow.cpp new file mode 100644 index 0000000000..8e811b094d --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/Controls/MaterialGenerateWindow.cpp @@ -0,0 +1,237 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModEditor/Controls/MaterialGenerateWindow.h" + +#include +#include +#include + +#include +#include +#include + +#include "Common/FileUtil.h" +#include "Common/IOFile.h" +#include "Common/Logging/Log.h" +#include "Common/StringUtil.h" +#include "Common/VariantUtil.h" + +#include "VideoCommon/Assets/MaterialAssetUtils.h" +#include "VideoCommon/Assets/TextureSamplerValue.h" +#include "VideoCommon/GraphicsModEditor/Controls/AssetDisplay.h" +#include "VideoCommon/GraphicsModEditor/Controls/MaterialControl.h" +#include "VideoCommon/GraphicsModEditor/EditorEvents.h" + +namespace GraphicsModEditor::Controls +{ +namespace +{ +void DrawSamplers(const VideoCommon::CustomAssetLibrary::AssetID& asset_id, + EditorState* editor_state, + std::span samplers, + std::span textures, + std::map* texture_filter, bool* valid) +{ + std::size_t texture_sampler_index = 0; + for (const auto& texture_sampler : samplers) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%s", texture_sampler.name.c_str()); + ImGui::TableNextColumn(); + + auto& texture = textures[texture_sampler_index]; + + auto [iter, inserted] = texture_filter->try_emplace(texture_sampler_index, "{IMAGE_1}"); + ImGui::InputText(fmt::format("##{}", texture_sampler.name).c_str(), &iter->second); + if (iter->second.find('{') == std::string::npos || iter->second.find('}') == std::string::npos) + { + *valid = false; + } + if (std::ranges::count(iter->second, '{') > 1 || std::ranges::count(iter->second, '}') > 1) + { + *valid = false; + } + + const auto sampler_origin_str = + VideoCommon::TextureSamplerValue::ToString(texture.sampler_origin); + if (ImGui::BeginCombo(fmt::format("##{}SamplerOrigin", texture_sampler.name).c_str(), + sampler_origin_str.c_str())) + { + static constexpr std::array + all_sampler_types = {VideoCommon::TextureSamplerValue::SamplerOrigin::Asset, + VideoCommon::TextureSamplerValue::SamplerOrigin::TextureHash}; + + for (const auto& type : all_sampler_types) + { + const bool is_selected = type == texture.sampler_origin; + const auto type_name = VideoCommon::TextureSamplerValue::ToString(type); + if (ImGui::Selectable(fmt::format("{}##{}", type_name, texture_sampler.name).c_str(), + is_selected)) + { + texture.sampler_origin = type; + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + } + ImGui::EndCombo(); + } + + texture_sampler_index++; + } +} + +void DrawMaterialGenerationContext(RasterMaterialGenerationContext* context, + VideoCommon::RasterShaderData* shader_data, bool* valid) +{ + if (ImGui::BeginTable("MaterialVertexPropertiesForm", 2)) + { + MaterialControl::DrawUniforms("", context->state, shader_data->m_vertex_properties, + &context->material_template_data.vertex_properties); + + ImGui::EndTable(); + } + + if (ImGui::BeginTable("MaterialPixelPropertiesForm", 2)) + { + MaterialControl::DrawUniforms("", context->state, shader_data->m_pixel_properties, + &context->material_template_data.pixel_properties); + DrawSamplers("", context->state, shader_data->m_pixel_samplers, + context->material_template_data.pixel_textures, + &context->material_property_index_to_texture_filter, valid); + + ImGui::EndTable(); + } + + MaterialControl::DrawProperties("", context->state, &context->material_template_data); + MaterialControl::DrawLinkedMaterial("", context->state, &context->material_template_data); +} +} // namespace +bool ShowMaterialGenerateWindow(RasterMaterialGenerationContext* context) +{ + bool valid = true; + bool result = false; + const std::string_view material_generate_popup = "Material Generate"; + if (!ImGui::IsPopupOpen(material_generate_popup.data())) + { + ImGui::OpenPopup(material_generate_popup.data()); + } + + const ImVec2 center = ImGui::GetMainViewport()->GetCenter(); + ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); + if (ImGui::BeginPopupModal(material_generate_popup.data(), nullptr)) + { + bool shader_changed = false; + if (ImGui::BeginTable("MaterialForm", 2)) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Shader"); + ImGui::TableNextColumn(); + + if (AssetDisplay("MaterialShaderAsset", context->state, + &context->material_template_data.shader_asset, AssetDataType::Shader)) + { + shader_changed = true; + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Texture Folder"); + ImGui::TableNextColumn(); + + ImGui::InputText("##TextureFolderPath", &context->input_path); + if (!File::Exists(context->input_path)) + { + valid = false; + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Output Folder"); + ImGui::TableNextColumn(); + + ImGui::InputText("##OutputFolderPath", &context->output_path); + + ImGui::EndTable(); + } + + auto asset = context->state->m_user_data.m_asset_library->GetAssetFromID( + context->material_template_data.shader_asset); + if (asset) + { + auto shader = std::get_if>(&asset->m_data); + if (!shader) + { + ImGui::Text("Asset id '%s' was not type raster shader!", + context->material_template_data.shader_asset.c_str()); + context->material_template_data.shader_asset = ""; + valid = false; + } + else + { + if (shader_changed) + { + SetMaterialPropertiesFromShader(**shader, &context->material_template_data); + SetMaterialTexturesFromShader(**shader, &context->material_template_data); + context->material_property_index_to_texture_filter.clear(); + } + if (!asset->m_valid) + { + valid = false; + ImGui::Text("The shader '%s' is invalid!", + context->material_template_data.shader_asset.c_str()); + } + else + { + if (valid) + { + DrawMaterialGenerationContext(context, shader->get(), &valid); + } + } + } + } + else + { + ImGui::Text("Please choose a shader for this material"); + valid = false; + } + + if (!valid) + { + ImGui::BeginDisabled(); + } + if (ImGui::Button("Import", ImVec2(120, 0))) + { + std::string error; + GenerateMaterials(context, &error); + if (error.empty()) + { + GraphicsModEditor::EditorEvents::ChangeOccurredEvent::Trigger(); + } + else + { + ERROR_LOG_FMT(VIDEO, "Failed to generate materials, error was '{}'", error); + } + ImGui::CloseCurrentPopup(); + result = true; + } + if (!valid) + { + ImGui::EndDisabled(); + } + + ImGui::SameLine(); + + if (ImGui::Button("Cancel", ImVec2(120, 0))) + { + result = true; + ImGui::CloseCurrentPopup(); + } + + ImGui::EndPopup(); + } + + return result; +} +} // namespace GraphicsModEditor::Controls diff --git a/Source/Core/VideoCommon/GraphicsModEditor/Controls/MaterialGenerateWindow.h b/Source/Core/VideoCommon/GraphicsModEditor/Controls/MaterialGenerateWindow.h new file mode 100644 index 0000000000..3e1cb3ed25 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/Controls/MaterialGenerateWindow.h @@ -0,0 +1,11 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "VideoCommon/GraphicsModEditor/MaterialGeneration.h" + +namespace GraphicsModEditor::Controls +{ +bool ShowMaterialGenerateWindow(RasterMaterialGenerationContext* context); +} diff --git a/Source/Core/VideoCommon/GraphicsModEditor/Controls/MeshControl.cpp b/Source/Core/VideoCommon/GraphicsModEditor/Controls/MeshControl.cpp new file mode 100644 index 0000000000..a27667f3f8 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/Controls/MeshControl.cpp @@ -0,0 +1,71 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModEditor/Controls/MeshControl.h" + +#include + +#include + +#include "Common/StringUtil.h" +#include "VideoCommon/Assets/TextureAsset.h" +#include "VideoCommon/GraphicsModEditor/Controls/AssetDisplay.h" +#include "VideoCommon/GraphicsModEditor/EditorEvents.h" + +namespace GraphicsModEditor::Controls +{ +MeshControl::MeshControl(EditorState& state) : m_state(state) +{ +} +void MeshControl::DrawImGui(const VideoCommon::CustomAssetLibrary::AssetID& asset_id, + VideoCommon::MeshData* mesh_data, const std::filesystem::path& path, + VideoCommon::CustomAssetLibrary::TimeType* last_data_write, + AbstractTexture* mesh_preview) +{ + if (ImGui::BeginTable("MeshForm", 2)) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("ID"); + ImGui::TableNextColumn(); + ImGui::Text("%s", asset_id.c_str()); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Name"); + ImGui::TableNextColumn(); + ImGui::TextWrapped("%s", PathToString(path.stem()).c_str()); + ImGui::EndTable(); + } + + if (ImGui::CollapsingHeader("Materials", ImGuiTreeNodeFlags_DefaultOpen)) + { + if (ImGui::BeginTable("MeshMaterialsForm", 2)) + { + for (auto& [mesh_material, material_asset_id] : + mesh_data->m_mesh_material_to_material_asset_id) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%s", mesh_material.c_str()); + ImGui::TableNextColumn(); + + if (AssetDisplay(mesh_material, &m_state, &material_asset_id, + AssetDataType::RasterMaterial)) + { + *last_data_write = std::chrono::system_clock::now(); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(material_asset_id); + } + } + ImGui::EndTable(); + } + } + + if (mesh_preview) + { + const auto ratio = mesh_preview->GetWidth() / mesh_preview->GetHeight(); + ImGui::Image(*mesh_preview, ImVec2{ImGui::GetContentRegionAvail().x, + ImGui::GetContentRegionAvail().x * ratio}); + } +} +} // namespace GraphicsModEditor::Controls diff --git a/Source/Core/VideoCommon/GraphicsModEditor/Controls/MeshControl.h b/Source/Core/VideoCommon/GraphicsModEditor/Controls/MeshControl.h new file mode 100644 index 0000000000..1066b6c463 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/Controls/MeshControl.h @@ -0,0 +1,31 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "VideoCommon/Assets/CustomAsset.h" +#include "VideoCommon/GraphicsModEditor/EditorState.h" + +namespace VideoCommon +{ +class AbstractTexture; +struct MeshData; +} // namespace VideoCommon + +namespace GraphicsModEditor::Controls +{ +class MeshControl +{ +public: + explicit MeshControl(EditorState& state); + void DrawImGui(const VideoCommon::CustomAssetLibrary::AssetID& asset_id, + VideoCommon::MeshData* mesh_data, const std::filesystem::path& path, + VideoCommon::CustomAssetLibrary::TimeType* last_data_write, + AbstractTexture* texture_preview); + +private: + EditorState& m_state; +}; +} // namespace GraphicsModEditor::Controls diff --git a/Source/Core/VideoCommon/GraphicsModEditor/Controls/MeshExtractWindow.cpp b/Source/Core/VideoCommon/GraphicsModEditor/Controls/MeshExtractWindow.cpp new file mode 100644 index 0000000000..80c7f3f3e8 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/Controls/MeshExtractWindow.cpp @@ -0,0 +1,79 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModEditor/Controls/MeshExtractWindow.h" + +#include + +#include +#include +#include + +#include "Common/CommonPaths.h" +#include "Common/FileUtil.h" +#include "Core/ConfigManager.h" + +namespace GraphicsModEditor::Controls +{ +bool ShowMeshExtractWindow(SceneDumper& scene_dumper, SceneDumper::RecordingRequest& request) +{ + bool result = false; + + const std::string_view mesh_extract_popup = "Mesh Extract"; + if (!ImGui::IsPopupOpen(mesh_extract_popup.data())) + { + ImGui::OpenPopup(mesh_extract_popup.data()); + } + + const ImVec2 center = ImGui::GetMainViewport()->GetCenter(); + ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); + if (ImGui::BeginPopupModal(mesh_extract_popup.data(), nullptr)) + { + ImGui::Checkbox("Enable Blending", &request.m_enable_blending); + ImGui::SetItemTooltip("Enable blending - any object marked with blending enabled will have " + "transparency turned on for each recorded object in the mesh output."); + + ImGui::Checkbox("Include Materials", &request.m_include_materials); + ImGui::SetItemTooltip( + "Include Materials - writes textures to Load/Textures and writes material entries that use " + "those textures for each recorded object in the mesh output."); + + ImGui::Checkbox("Include Transforms", &request.m_include_transform); + ImGui::SetItemTooltip("Include Transforms - writes the position, rotation, and scale of " + "each recorded object in the mesh output."); + + ImGui::Checkbox("Apply GPU Skinning", &request.m_apply_gpu_skinning); + ImGui::SetItemTooltip("Apply GPU Skinning - if a mesh uses GPU skinning " + " and this is disabled, mesh captured will contain whatever state " + "defined by the game (some games may use a T pose or Rest pose), " + "otherwise applies the transformation as visible when captured"); + + ImGui::Checkbox("Ignore Orthographic Draws", &request.m_ignore_orthographic); + ImGui::SetItemTooltip( + "Ignore Orthographic Draws - ignores draws done using an orthographic projection. " + "This typically includes 2d elements like the HUD or EFB copies."); + + if (ImGui::Button("Extract", ImVec2(120, 0))) + { + const std::string path_prefix = + File::GetUserPath(D_DUMPMESHES_IDX) + SConfig::GetInstance().GetGameID(); + + const std::time_t start_time = std::time(nullptr); + const std::string base_name = + fmt::format("{}_{:%Y-%m-%d_%H-%M-%S}", path_prefix, fmt::localtime(start_time)); + const std::string path = fmt::format("{}.gltf", base_name); + scene_dumper.Record(path, request); + ImGui::CloseCurrentPopup(); + result = true; + } + ImGui::SameLine(); + if (ImGui::Button("Cancel", ImVec2(120, 0))) + { + ImGui::CloseCurrentPopup(); + result = true; + } + ImGui::EndPopup(); + } + return result; +} +} // namespace GraphicsModEditor::Controls diff --git a/Source/Core/VideoCommon/GraphicsModEditor/Controls/MeshExtractWindow.h b/Source/Core/VideoCommon/GraphicsModEditor/Controls/MeshExtractWindow.h new file mode 100644 index 0000000000..50656db1a6 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/Controls/MeshExtractWindow.h @@ -0,0 +1,13 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "VideoCommon/GraphicsModEditor/SceneDumper.h" + +namespace GraphicsModEditor::Controls +{ +bool ShowMeshExtractWindow(SceneDumper& scene_dumper, SceneDumper::RecordingRequest& request); +} diff --git a/Source/Core/VideoCommon/GraphicsModEditor/Controls/MeshImportWindow.cpp b/Source/Core/VideoCommon/GraphicsModEditor/Controls/MeshImportWindow.cpp new file mode 100644 index 0000000000..dbb55431c6 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/Controls/MeshImportWindow.cpp @@ -0,0 +1,96 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModEditor/Controls/MeshImportWindow.h" + +#include + +#include +#include + +#include "Common/FileUtil.h" +#include "Common/IOFile.h" +#include "Common/Logging/Log.h" +#include "Common/StringUtil.h" + +#include "VideoCommon/Assets/MeshAsset.h" + +namespace GraphicsModEditor::Controls +{ +bool ShowMeshImportWindow(std::string_view filename, bool* import_materials) +{ + bool result = false; + const std::string_view mesh_import_popup = "Mesh Import"; + if (!ImGui::IsPopupOpen(mesh_import_popup.data())) + { + ImGui::OpenPopup(mesh_import_popup.data()); + } + + const ImVec2 center = ImGui::GetMainViewport()->GetCenter(); + ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); + if (ImGui::BeginPopupModal(mesh_import_popup.data(), nullptr)) + { + ImGui::BeginDisabled(); + ImGui::Checkbox("Import Materials", import_materials); + ImGui::SetItemTooltip( + "Import Materials - materials from the mesh will be created as Dolphin materials."); + ImGui::EndDisabled(); + + if (ImGui::Button("Import", ImVec2(120, 0))) + { + VideoCommon::MeshData mesh_data; + if (!VideoCommon::MeshData::FromGLTF(filename, &mesh_data)) + { + // TODO: move this to the UI + ERROR_LOG_FMT(VIDEO, "Failed to read GLTF mesh '{}'", filename); + } + else + { + std::string basename; + std::string basepath; + SplitPath(filename, &basepath, &basename, nullptr); + + const std::string dolphin_mesh_filename = basepath + basename + ".dolmesh"; + File::IOFile outbound_file(dolphin_mesh_filename, "wb"); + if (!VideoCommon::MeshData::ToDolphinMesh(&outbound_file, mesh_data)) + { + // TODO: move this to the UI + ERROR_LOG_FMT(VIDEO, "Failed to write Dolphin mesh '{}'", dolphin_mesh_filename); + } + else + { + const std::string dolphin_json_filename = basepath + basename + ".metadata"; + std::ofstream json_stream; + File::OpenFStream(json_stream, dolphin_json_filename, std::ios_base::out); + if (!json_stream.is_open()) + { + ERROR_LOG_FMT(VIDEO, "Failed to open metadata file '{}' for writing", + dolphin_json_filename); + ImGui::CloseCurrentPopup(); + } + else + { + picojson::object serialized_root; + VideoCommon::MeshData::ToJson(serialized_root, mesh_data); + const auto output = picojson::value{serialized_root}.serialize(true); + json_stream << output; + } + } + + ImGui::CloseCurrentPopup(); + result = true; + } + } + + if (ImGui::Button("Cancel", ImVec2(120, 0))) + { + ImGui::CloseCurrentPopup(); + result = true; + } + + ImGui::EndPopup(); + } + + return result; +} +} // namespace GraphicsModEditor::Controls diff --git a/Source/Core/VideoCommon/GraphicsModEditor/Controls/MeshImportWindow.h b/Source/Core/VideoCommon/GraphicsModEditor/Controls/MeshImportWindow.h new file mode 100644 index 0000000000..fe82aeeeba --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/Controls/MeshImportWindow.h @@ -0,0 +1,11 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +namespace GraphicsModEditor::Controls +{ +bool ShowMeshImportWindow(std::string_view filename, bool* import_materials); +} diff --git a/Source/Core/VideoCommon/GraphicsModEditor/Controls/MiscControls.cpp b/Source/Core/VideoCommon/GraphicsModEditor/Controls/MiscControls.cpp new file mode 100644 index 0000000000..eaffde08d0 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/Controls/MiscControls.cpp @@ -0,0 +1,46 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModEditor/Controls/MiscControls.h" + +namespace GraphicsModEditor::Controls +{ +bool ColorButton(const char* label, ImVec2 buttonSize, ImVec4 color) +{ + ImGui::PushStyleColor(ImGuiCol_Button, color); + bool pressed = ImGui::Button(label, buttonSize); + + // Draw a gradient on top of the button + { + ImVec2 tl = ImGui::GetItemRectMin(); + ImVec2 br = ImGui::GetItemRectMax(); + ImVec2 size = ImGui::GetItemRectSize(); + + float k = 0.3f; + + ImVec2 tl_middle(tl.x, tl.y + size.y * (1.f - k)); + ImVec2 br_middle(br.x, tl.y + size.y * k); + + ImVec4 col_darker(0.f, 0.f, 0.f, 0.2f); + ImVec4 col_interm(0.f, 0.f, 0.f, 0.1f); + ImVec4 col_transp(0.f, 0.f, 0.f, 0.f); + + ImGui::GetForegroundDrawList()->AddRectFilledMultiColor( + tl, br_middle, + ImGui::GetColorU32(col_interm), // upper left + ImGui::GetColorU32(col_interm), // upper right + ImGui::GetColorU32(col_transp), // bottom right + ImGui::GetColorU32(col_transp) // bottom left + ); + ImGui::GetForegroundDrawList()->AddRectFilledMultiColor( + tl_middle, br, + ImGui::GetColorU32(col_transp), // upper left + ImGui::GetColorU32(col_transp), // upper right + ImGui::GetColorU32(col_darker), // bottom right + ImGui::GetColorU32(col_darker) // bottom left + ); + } + ImGui::PopStyleColor(); + return pressed; +} +} // namespace GraphicsModEditor::Controls diff --git a/Source/Core/VideoCommon/GraphicsModEditor/Controls/MiscControls.h b/Source/Core/VideoCommon/GraphicsModEditor/Controls/MiscControls.h new file mode 100644 index 0000000000..69c02bf278 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/Controls/MiscControls.h @@ -0,0 +1,12 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +namespace GraphicsModEditor::Controls +{ +// Pulled from the tips and tricks cafe on 2023, credit to pthom +bool ColorButton(const char* label, ImVec2 buttonSize, ImVec4 color); +} // namespace GraphicsModEditor::Controls diff --git a/Source/Core/VideoCommon/GraphicsModEditor/Controls/RenderTargetControl.cpp b/Source/Core/VideoCommon/GraphicsModEditor/Controls/RenderTargetControl.cpp new file mode 100644 index 0000000000..8b829d4043 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/Controls/RenderTargetControl.cpp @@ -0,0 +1,159 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModEditor/Controls/RenderTargetControl.h" + +#include + +#include + +#include "Common/StringUtil.h" +#include "VideoCommon/Assets/TextureAsset.h" +#include "VideoCommon/GraphicsModEditor/EditorEvents.h" + +namespace GraphicsModEditor::Controls +{ +RenderTargetControl::RenderTargetControl(EditorState& state) : m_state(state) +{ +} +void RenderTargetControl::DrawImGui(const VideoCommon::CustomAssetLibrary::AssetID& asset_id, + VideoCommon::RenderTargetData* render_target_data, + const std::filesystem::path& path, + VideoCommon::CustomAssetLibrary::TimeType* last_data_write, + AbstractTexture* texture_preview) +{ + if (ImGui::BeginTable("TextureForm", 2)) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("ID"); + ImGui::TableNextColumn(); + ImGui::Text("%s", asset_id.c_str()); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Name"); + ImGui::TableNextColumn(); + ImGui::TextWrapped("%s", PathToString(path.stem()).c_str()); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Format"); + ImGui::TableNextColumn(); + ImGui::Text("RGBA8"); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Min Filter Mode"); + ImGui::TableNextColumn(); + if (ImGui::BeginCombo("##MinFilterMode", + fmt::to_string(render_target_data->m_sampler.tm0.min_filter).c_str())) + { + for (auto e = FilterMode::Near; e <= FilterMode::Linear; + e = static_cast(static_cast(e) + 1)) + { + const bool is_selected = render_target_data->m_sampler.tm0.min_filter == e; + if (ImGui::Selectable(fmt::to_string(e).c_str(), is_selected)) + { + render_target_data->m_sampler.tm0.min_filter = e; + *last_data_write = std::chrono::system_clock::now(); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + } + ImGui::EndCombo(); + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Mag Filter Mode"); + ImGui::TableNextColumn(); + if (ImGui::BeginCombo("##MagFilterMode", + fmt::to_string(render_target_data->m_sampler.tm0.mag_filter).c_str())) + { + for (auto e = FilterMode::Near; e <= FilterMode::Linear; + e = static_cast(static_cast(e) + 1)) + { + const bool is_selected = render_target_data->m_sampler.tm0.mag_filter == e; + if (ImGui::Selectable(fmt::to_string(e).c_str(), is_selected)) + { + render_target_data->m_sampler.tm0.mag_filter = e; + *last_data_write = std::chrono::system_clock::now(); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + } + ImGui::EndCombo(); + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Mip Filter Mode"); + ImGui::TableNextColumn(); + if (ImGui::BeginCombo("##MipFilterMode", + fmt::to_string(render_target_data->m_sampler.tm0.mipmap_filter).c_str())) + { + for (auto e = FilterMode::Near; e <= FilterMode::Linear; + e = static_cast(static_cast(e) + 1)) + { + const bool is_selected = render_target_data->m_sampler.tm0.mipmap_filter == e; + if (ImGui::Selectable(fmt::to_string(e).c_str(), is_selected)) + { + render_target_data->m_sampler.tm0.mipmap_filter = e; + *last_data_write = std::chrono::system_clock::now(); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + } + ImGui::EndCombo(); + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("U Wrap Mode"); + ImGui::TableNextColumn(); + if (ImGui::BeginCombo("##UWrapMode", + fmt::to_string(render_target_data->m_sampler.tm0.wrap_u).c_str())) + { + for (auto e = WrapMode::Clamp; e <= WrapMode::Mirror; + e = static_cast(static_cast(e) + 1)) + { + const bool is_selected = render_target_data->m_sampler.tm0.wrap_u == e; + if (ImGui::Selectable(fmt::to_string(e).c_str(), is_selected)) + { + render_target_data->m_sampler.tm0.wrap_u = e; + *last_data_write = std::chrono::system_clock::now(); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + } + ImGui::EndCombo(); + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("V Wrap Mode"); + ImGui::TableNextColumn(); + if (ImGui::BeginCombo("##VWrapMode", + fmt::to_string(render_target_data->m_sampler.tm0.wrap_v).c_str())) + { + for (auto e = WrapMode::Clamp; e <= WrapMode::Mirror; + e = static_cast(static_cast(e) + 1)) + { + const bool is_selected = render_target_data->m_sampler.tm0.wrap_v == e; + if (ImGui::Selectable(fmt::to_string(e).c_str(), is_selected)) + { + render_target_data->m_sampler.tm0.wrap_v = e; + *last_data_write = std::chrono::system_clock::now(); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + } + ImGui::EndCombo(); + } + ImGui::EndTable(); + + if (texture_preview) + { + const auto ratio = texture_preview->GetWidth() / texture_preview->GetHeight(); + ImGui::Image(*texture_preview, ImVec2{ImGui::GetContentRegionAvail().x, + ImGui::GetContentRegionAvail().x * ratio}); + } + } +} +} // namespace GraphicsModEditor::Controls diff --git a/Source/Core/VideoCommon/GraphicsModEditor/Controls/RenderTargetControl.h b/Source/Core/VideoCommon/GraphicsModEditor/Controls/RenderTargetControl.h new file mode 100644 index 0000000000..dcbe2c0e8d --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/Controls/RenderTargetControl.h @@ -0,0 +1,32 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "VideoCommon/Assets/CustomAsset.h" +#include "VideoCommon/GraphicsModEditor/EditorState.h" + +namespace VideoCommon +{ +class AbstractTexture; +struct RenderTargetData; +} // namespace VideoCommon + +namespace GraphicsModEditor::Controls +{ +class RenderTargetControl +{ +public: + explicit RenderTargetControl(EditorState& state); + void DrawImGui(const VideoCommon::CustomAssetLibrary::AssetID& asset_id, + VideoCommon::RenderTargetData* render_target_data, + const std::filesystem::path& path, + VideoCommon::CustomAssetLibrary::TimeType* last_data_write, + AbstractTexture* texture_preview); + +private: + EditorState& m_state; +}; +} // namespace GraphicsModEditor::Controls diff --git a/Source/Core/VideoCommon/GraphicsModEditor/Controls/ShaderControl.cpp b/Source/Core/VideoCommon/GraphicsModEditor/Controls/ShaderControl.cpp new file mode 100644 index 0000000000..f510117a77 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/Controls/ShaderControl.cpp @@ -0,0 +1,503 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModEditor/Controls/ShaderControl.h" + +#include +#include +#include + +#include +#include +#include + +#include "Common/VariantUtil.h" + +#include "VideoCommon/Assets/ShaderAsset.h" +#include "VideoCommon/GraphicsModEditor/Controls/AssetDisplay.h" +#include "VideoCommon/GraphicsModEditor/EditorEvents.h" + +namespace GraphicsModEditor::Controls +{ +ShaderControl::ShaderControl(EditorState& state) : m_state(state) +{ +} + +void ShaderControl::DrawImGui(const VideoCommon::CustomAssetLibrary::AssetID& asset_id, + VideoCommon::PixelShaderData* shader, + VideoCommon::CustomAssetLibrary::TimeType* last_data_write) +{ + if (ImGui::BeginTable("ShaderForm", 2)) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("ID"); + ImGui::TableNextColumn(); + ImGui::Text("%s", asset_id.c_str()); + + ImGui::EndTable(); + } + + if (ImGui::CollapsingHeader("Properties", ImGuiTreeNodeFlags_DefaultOpen)) + { + for (auto& entry : shader->m_properties) + { + // C++20: error with capturing structured bindings for our version of clang + auto& name = entry.first; + auto& property = entry.second; + + ImGui::Text("%s", name.c_str()); + ImGui::SameLine(); + // TODO Button for editing description + ImGui::SameLine(); + std::visit( + overloaded{ + [&](VideoCommon::ShaderProperty::Sampler2D& default_value) { + if (AssetDisplay(name, &m_state, &default_value.value.asset, + AssetDataType::Texture)) + { + *last_data_write = std::chrono::system_clock::now(); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + }, + [&](VideoCommon::ShaderProperty::Sampler2DArray& default_value) { + if (AssetDisplay(name, &m_state, &default_value.value.asset, + AssetDataType::Texture)) + { + *last_data_write = std::chrono::system_clock::now(); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + }, + [&](VideoCommon::ShaderProperty::SamplerCube& default_value) { + if (AssetDisplay(name, &m_state, &default_value.value.asset, + AssetDataType::Texture)) + { + *last_data_write = std::chrono::system_clock::now(); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + }, + [&](s32& default_value) { + if (ImGui::InputInt(fmt::format("##{}", name).c_str(), &default_value)) + { + *last_data_write = std::chrono::system_clock::now(); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + }, + [&](std::array& default_value) { + if (ImGui::InputInt2(fmt::format("##{}", name).c_str(), default_value.data())) + { + *last_data_write = std::chrono::system_clock::now(); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + }, + [&](std::array& default_value) { + if (ImGui::InputInt3(fmt::format("##{}", name).c_str(), default_value.data())) + { + *last_data_write = std::chrono::system_clock::now(); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + }, + [&](std::array& default_value) { + if (ImGui::InputInt4(fmt::format("##{}", name).c_str(), default_value.data())) + { + *last_data_write = std::chrono::system_clock::now(); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + }, + [&](float& default_value) { + if (ImGui::InputFloat(fmt::format("##{}", name).c_str(), &default_value)) + { + *last_data_write = std::chrono::system_clock::now(); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + }, + [&](std::array& default_value) { + if (ImGui::InputFloat2(fmt::format("##{}", name).c_str(), default_value.data())) + { + *last_data_write = std::chrono::system_clock::now(); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + }, + [&](std::array& default_value) { + if (ImGui::InputFloat3(fmt::format("##{}", name).c_str(), default_value.data())) + { + *last_data_write = std::chrono::system_clock::now(); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + }, + [&](std::array& default_value) { + if (ImGui::InputFloat4(fmt::format("##{}", name).c_str(), default_value.data())) + { + *last_data_write = std::chrono::system_clock::now(); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + }, + [&](VideoCommon::ShaderProperty::RGB& default_value) { + if (ImGui::ColorEdit3(fmt::format("##{}", name).c_str(), + default_value.value.data())) + { + *last_data_write = std::chrono::system_clock::now(); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + }, + [&](VideoCommon::ShaderProperty::RGBA& default_value) { + if (ImGui::ColorEdit4(fmt::format("##{}", name).c_str(), + default_value.value.data())) + { + *last_data_write = std::chrono::system_clock::now(); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + }, + [&](bool& default_value) { + if (ImGui::Checkbox(fmt::format("##{}", name).c_str(), &default_value)) + { + *last_data_write = std::chrono::system_clock::now(); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + }}, + property.m_default); + ImGui::SameLine(); + if (ImGui::Button("X")) + { + shader->m_properties.erase(name); + } + } + + ImGui::Separator(); + + if (ImGui::Button("Add")) + { + m_add_property_name = ""; + m_add_property_chosen_type = ""; + m_add_property_data = {}; + ImGui::OpenPopup("AddShaderPropPopup"); + } + + if (ImGui::BeginPopupModal("AddShaderPropPopup", nullptr)) + { + if (ImGui::BeginTable("AddShaderPropForm", 2)) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Name"); + ImGui::TableNextColumn(); + ImGui::InputText("##PropName", &m_add_property_name); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Type"); + ImGui::TableNextColumn(); + + if (ImGui::BeginCombo("##PropType", m_add_property_chosen_type.c_str())) + { + for (const auto& type : VideoCommon::ShaderProperty::GetValueTypeNames()) + { + const bool is_selected = type == m_add_property_chosen_type; + if (ImGui::Selectable(type.data(), is_selected)) + { + m_add_property_chosen_type = type; + m_add_property_data = VideoCommon::ShaderProperty::GetDefaultValueFromTypeName(type); + } + } + ImGui::EndCombo(); + } + + ImGui::EndTable(); + } + if (ImGui::Button("Add")) + { + VideoCommon::ShaderProperty shader_property; + shader_property.m_description = ""; + shader_property.m_default = m_add_property_data; + shader->m_properties.insert_or_assign(m_add_property_name, std::move(shader_property)); + *last_data_write = std::chrono::system_clock::now(); + GraphicsModEditor::EditorEvents::ChangeOccurredEvent::Trigger(); + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (ImGui::Button("Cancel")) + { + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + } +} + +void ShaderControl::DrawImGui(const VideoCommon::CustomAssetLibrary::AssetID& asset_id, + VideoCommon::RasterShaderData* shader) +{ + if (ImGui::BeginTable("ShaderForm", 2)) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("ID"); + ImGui::TableNextColumn(); + ImGui::Text("%s", asset_id.c_str()); + + ImGui::EndTable(); + } + + const auto parse_properties = + [&](std::map& properties) { + std::vector properties_to_erase; + for (auto& entry : properties) + { + // C++20: error with capturing structured bindings for our version of clang + auto& name = entry.first; + auto& property = entry.second; + + ImGui::Text("%s", name.c_str()); + ImGui::SameLine(); + // TODO Button for editing description + ImGui::SameLine(); + std::visit( + overloaded{ + [&](s32& default_value) { + if (ImGui::InputInt(fmt::format("##{}", name).c_str(), &default_value)) + { + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + }, + [&](std::array& default_value) { + if (ImGui::InputInt2(fmt::format("##{}", name).c_str(), default_value.data())) + { + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + }, + [&](std::array& default_value) { + if (ImGui::InputInt3(fmt::format("##{}", name).c_str(), default_value.data())) + { + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + }, + [&](std::array& default_value) { + if (ImGui::InputInt4(fmt::format("##{}", name).c_str(), default_value.data())) + { + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + }, + [&](float& default_value) { + if (ImGui::InputFloat(fmt::format("##{}", name).c_str(), &default_value)) + { + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + }, + [&](std::array& default_value) { + if (ImGui::InputFloat2(fmt::format("##{}", name).c_str(), default_value.data())) + { + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + }, + [&](std::array& default_value) { + if (ImGui::InputFloat3(fmt::format("##{}", name).c_str(), default_value.data())) + { + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + }, + [&](std::array& default_value) { + if (ImGui::InputFloat4(fmt::format("##{}", name).c_str(), default_value.data())) + { + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + }, + [&](VideoCommon::ShaderProperty2::RGB& default_value) { + if (ImGui::ColorEdit3(fmt::format("##{}", name).c_str(), + default_value.value.data())) + { + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + }, + [&](VideoCommon::ShaderProperty2::RGBA& default_value) { + if (ImGui::ColorEdit4(fmt::format("##{}", name).c_str(), + default_value.value.data())) + { + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + }, + [&](bool& default_value) { + if (ImGui::Checkbox(fmt::format("##{}", name).c_str(), &default_value)) + { + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + }}, + property.m_default); + ImGui::SameLine(); + if (ImGui::Button("X")) + { + properties_to_erase.push_back(name); + } + } + + for (const auto& name : properties_to_erase) + { + properties.erase(name); + } + }; + + if (ImGui::CollapsingHeader("Vertex Properties", ImGuiTreeNodeFlags_DefaultOpen)) + { + parse_properties(shader->m_vertex_properties); + ImGui::Separator(); + AddShaderProperty(shader, &shader->m_vertex_properties, "vertex"); + } + + if (ImGui::CollapsingHeader("Pixel Properties", ImGuiTreeNodeFlags_DefaultOpen)) + { + parse_properties(shader->m_pixel_properties); + ImGui::Separator(); + AddShaderProperty(shader, &shader->m_pixel_properties, "pixel"); + } + + if (ImGui::CollapsingHeader("Pixel Textures", ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::PushID("Sampler"); + std::vector sampler_to_erase; + for (std::size_t i = 0; i < shader->m_pixel_samplers.size(); i++) + { + ImGui::PushID(static_cast(i)); + auto& sampler = shader->m_pixel_samplers[i]; + ImGui::InputText("Name", &sampler.name, ImGuiInputTextFlags_CharsNoBlank); + ImGui::SameLine(); + + if (ImGui::BeginCombo("##TextureType", fmt::to_string(sampler.type).c_str())) + { + static constexpr std::array all_texture_types = { + AbstractTextureType::Texture_2D, AbstractTextureType::Texture_2DArray, + AbstractTextureType::Texture_CubeMap}; + + for (const auto& texture_type : all_texture_types) + { + const bool is_selected = texture_type == sampler.type; + const auto texture_type_str = fmt::to_string(texture_type); + if (ImGui::Selectable(fmt::format("{}##", texture_type_str).c_str(), is_selected)) + { + sampler.type = texture_type; + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + } + ImGui::EndCombo(); + } + + ImGui::SameLine(); + if (ImGui::Button("X")) + { + sampler_to_erase.push_back(&sampler); + } + ImGui::PopID(); + } + + for (const auto sampler : sampler_to_erase) + { + std::erase(shader->m_pixel_samplers, *sampler); + } + + if (ImGui::Button("Add")) + { + VideoCommon::RasterShaderData::SamplerData sampler_data; + sampler_data.name = ""; + sampler_data.type = AbstractTextureType::Texture_2D; + shader->m_pixel_samplers.push_back(std::move(sampler_data)); + GraphicsModEditor::EditorEvents::ChangeOccurredEvent::Trigger(); + } + + ImGui::PopID(); + } + + if (ImGui::CollapsingHeader("Pixel Output Target", ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::PushID("Output Target"); + std::vector output_targets_to_erase; + for (std::size_t i = 0; i < shader->m_output_targets.size(); i++) + { + ImGui::PushID(static_cast(i)); + auto& output_target = shader->m_output_targets[i]; + ImGui::InputText("Name", &output_target.name, ImGuiInputTextFlags_CharsNoBlank); + + ImGui::SameLine(); + if (ImGui::Button("X")) + { + output_targets_to_erase.push_back(&output_target); + } + ImGui::PopID(); + } + + for (const auto output_target : output_targets_to_erase) + { + std::erase(shader->m_output_targets, *output_target); + } + + if (ImGui::Button("Add")) + { + VideoCommon::RasterShaderData::OutputTargetData output_target; + output_target.name = ""; + shader->m_output_targets.push_back(std::move(output_target)); + GraphicsModEditor::EditorEvents::ChangeOccurredEvent::Trigger(); + } + + ImGui::PopID(); + } +} + +void ShaderControl::AddShaderProperty( + VideoCommon::RasterShaderData* shader, + std::map* properties, std::string_view type) +{ + if (ImGui::Button(fmt::format("Add {} property", type).c_str())) + { + m_add_property_name = ""; + m_add_property_chosen_type = ""; + m_add_property2_data = {}; + ImGui::OpenPopup(fmt::format("AddShader{}PropPopup", type).c_str()); + } + + if (ImGui::BeginPopupModal(fmt::format("AddShader{}PropPopup", type).c_str(), nullptr)) + { + if (ImGui::BeginTable(fmt::format("AddShader{}PropForm", type).c_str(), 2)) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Name"); + ImGui::TableNextColumn(); + ImGui::InputText(fmt::format("##PropName{}", type).c_str(), &m_add_property_name); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Type"); + ImGui::TableNextColumn(); + + if (ImGui::BeginCombo(fmt::format("##PropType{}", type).c_str(), + m_add_property_chosen_type.c_str())) + { + for (const auto& type_name : VideoCommon::ShaderProperty2::GetValueTypeNames()) + { + const bool is_selected = type_name == m_add_property_chosen_type; + if (ImGui::Selectable(type.data(), is_selected)) + { + m_add_property_chosen_type = type_name; + m_add_property2_data = VideoCommon::ShaderProperty2::GetDefaultValueFromTypeName(type); + } + } + ImGui::EndCombo(); + } + + ImGui::EndTable(); + } + if (ImGui::Button("Add")) + { + VideoCommon::ShaderProperty2 shader_property; + shader_property.m_description = ""; + shader_property.m_default = m_add_property2_data; + shader->m_pixel_properties.insert_or_assign(m_add_property_name, std::move(shader_property)); + GraphicsModEditor::EditorEvents::ChangeOccurredEvent::Trigger(); + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (ImGui::Button("Cancel")) + { + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } +} +} // namespace GraphicsModEditor::Controls diff --git a/Source/Core/VideoCommon/GraphicsModEditor/Controls/ShaderControl.h b/Source/Core/VideoCommon/GraphicsModEditor/Controls/ShaderControl.h new file mode 100644 index 0000000000..0bd86868d0 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/Controls/ShaderControl.h @@ -0,0 +1,35 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "VideoCommon/Assets/CustomAsset.h" +#include "VideoCommon/Assets/ShaderAsset.h" +#include "VideoCommon/GraphicsModEditor/EditorState.h" + +namespace GraphicsModEditor::Controls +{ +class ShaderControl +{ +public: + explicit ShaderControl(EditorState& state); + void DrawImGui(const VideoCommon::CustomAssetLibrary::AssetID& asset_id, + VideoCommon::PixelShaderData* shader, + VideoCommon::CustomAssetLibrary::TimeType* last_data_write); + void DrawImGui(const VideoCommon::CustomAssetLibrary::AssetID& asset_id, + VideoCommon::RasterShaderData* shader); + +private: + void AddShaderProperty(VideoCommon::RasterShaderData* shader, + std::map* properties, + std::string_view type); + std::string m_add_property_name; + std::string m_add_property_chosen_type; + VideoCommon::ShaderProperty::Value m_add_property_data; + VideoCommon::ShaderProperty2::Value m_add_property2_data; + EditorState& m_state; +}; +} // namespace GraphicsModEditor::Controls diff --git a/Source/Core/VideoCommon/GraphicsModEditor/Controls/TagSelectionWindow.cpp b/Source/Core/VideoCommon/GraphicsModEditor/Controls/TagSelectionWindow.cpp new file mode 100644 index 0000000000..e6f3ff3eb5 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/Controls/TagSelectionWindow.cpp @@ -0,0 +1,77 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModEditor/Controls/TagSelectionWindow.h" + +#include "VideoCommon/GraphicsModEditor/Controls/MiscControls.h" + +namespace GraphicsModEditor::Controls +{ +bool TagSelectionWindow(std::string_view popup_name, EditorState* state, std::string* chosen_tag) +{ + if (!state) [[unlikely]] + return false; + if (!chosen_tag) [[unlikely]] + return false; + + if (!ImGui::IsPopupOpen(popup_name.data())) + { + ImGui::OpenPopup(popup_name.data()); + } + + bool changed = false; + const ImVec2 center = ImGui::GetMainViewport()->GetCenter(); + ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); + if (ImGui::BeginPopupModal(popup_name.data(), nullptr)) + { + const u32 column_count = 5; + u32 current_columns = 0; + u32 tags_displayed = 0; + + if (ImGui::BeginTable("TagSelectionPopup", column_count)) + { + ImGui::TableNextRow(); + for (const auto& [tag_name, tag] : state->m_editor_data.m_tags) + { + tags_displayed++; + ImGui::TableNextColumn(); + + const bool tag_clicked = ColorButton( + tag.m_name.c_str(), tag_size, ImVec4{tag.m_color.x, tag.m_color.y, tag.m_color.z, 1}); + if (!tag.m_description.empty()) + ImGui::SetItemTooltip("%s", tag.m_description.c_str()); + if (tag_clicked) + { + *chosen_tag = tag.m_name; + changed = true; + ImGui::CloseCurrentPopup(); + } + + current_columns++; + if (current_columns == column_count) + { + ImGui::TableNextRow(); + current_columns = 0; + } + } + + if (ImGui::Button("Cancel", ImVec2(120, 0))) + { + changed = true; + ImGui::CloseCurrentPopup(); + } + + ImGui::EndTable(); + } + + if (tags_displayed == 0) + { + ImGui::Text("No tags found"); + } + + ImGui::EndPopup(); + } + + return changed; +} +} // namespace GraphicsModEditor::Controls diff --git a/Source/Core/VideoCommon/GraphicsModEditor/Controls/TagSelectionWindow.h b/Source/Core/VideoCommon/GraphicsModEditor/Controls/TagSelectionWindow.h new file mode 100644 index 0000000000..99f0c25be1 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/Controls/TagSelectionWindow.h @@ -0,0 +1,18 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "imgui.h" + +#include "VideoCommon/GraphicsModEditor/EditorState.h" + +namespace GraphicsModEditor::Controls +{ +static inline ImVec2 tag_size = ImVec2(175, 35); + +bool TagSelectionWindow(std::string_view popup_name, EditorState* state, std::string* chosen_tag); +} // namespace GraphicsModEditor::Controls diff --git a/Source/Core/VideoCommon/GraphicsModEditor/Controls/TextureControl.cpp b/Source/Core/VideoCommon/GraphicsModEditor/Controls/TextureControl.cpp new file mode 100644 index 0000000000..2ba922a89c --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/Controls/TextureControl.cpp @@ -0,0 +1,179 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModEditor/Controls/TextureControl.h" + +#include + +#include + +#include "Common/StringUtil.h" +#include "VideoCommon/Assets/TextureAsset.h" +#include "VideoCommon/GraphicsModEditor/EditorEvents.h" + +namespace GraphicsModEditor::Controls +{ +TextureControl::TextureControl(EditorState& state) : m_state(state) +{ +} +void TextureControl::DrawImGui(const VideoCommon::CustomAssetLibrary::AssetID& asset_id, + VideoCommon::TextureData* texture_data, + const std::filesystem::path& path, + VideoCommon::CustomAssetLibrary::TimeType* last_data_write, + AbstractTexture* texture_preview) +{ + if (ImGui::BeginTable("TextureForm", 2)) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("ID"); + ImGui::TableNextColumn(); + ImGui::Text("%s", asset_id.c_str()); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Name"); + ImGui::TableNextColumn(); + ImGui::TextWrapped("%s", PathToString(path.stem()).c_str()); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Type"); + ImGui::TableNextColumn(); + if (ImGui::BeginCombo("##TextureType", fmt::to_string(texture_data->m_type).c_str())) + { + for (auto e = VideoCommon::TextureData::Type::Type_Undefined; + e <= VideoCommon::TextureData::Type::Type_Max; + e = static_cast(static_cast(e) + 1)) + { + if (e == VideoCommon::TextureData::Type::Type_Undefined) + { + continue; + } + + const bool is_selected = texture_data->m_type == e; + if (ImGui::Selectable(fmt::to_string(e).c_str(), is_selected)) + { + texture_data->m_type = e; + *last_data_write = std::chrono::system_clock::now(); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + } + ImGui::EndCombo(); + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Min Filter Mode"); + ImGui::TableNextColumn(); + if (ImGui::BeginCombo("##MinFilterMode", + fmt::to_string(texture_data->m_sampler.tm0.min_filter).c_str())) + { + for (auto e = FilterMode::Near; e <= FilterMode::Linear; + e = static_cast(static_cast(e) + 1)) + { + const bool is_selected = texture_data->m_sampler.tm0.min_filter == e; + if (ImGui::Selectable(fmt::to_string(e).c_str(), is_selected)) + { + texture_data->m_sampler.tm0.min_filter = e; + *last_data_write = std::chrono::system_clock::now(); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + } + ImGui::EndCombo(); + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Mag Filter Mode"); + ImGui::TableNextColumn(); + if (ImGui::BeginCombo("##MagFilterMode", + fmt::to_string(texture_data->m_sampler.tm0.mag_filter).c_str())) + { + for (auto e = FilterMode::Near; e <= FilterMode::Linear; + e = static_cast(static_cast(e) + 1)) + { + const bool is_selected = texture_data->m_sampler.tm0.mag_filter == e; + if (ImGui::Selectable(fmt::to_string(e).c_str(), is_selected)) + { + texture_data->m_sampler.tm0.mag_filter = e; + *last_data_write = std::chrono::system_clock::now(); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + } + ImGui::EndCombo(); + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Mip Filter Mode"); + ImGui::TableNextColumn(); + if (ImGui::BeginCombo("##MipFilterMode", + fmt::to_string(texture_data->m_sampler.tm0.mipmap_filter).c_str())) + { + for (auto e = FilterMode::Near; e <= FilterMode::Linear; + e = static_cast(static_cast(e) + 1)) + { + const bool is_selected = texture_data->m_sampler.tm0.mipmap_filter == e; + if (ImGui::Selectable(fmt::to_string(e).c_str(), is_selected)) + { + texture_data->m_sampler.tm0.mipmap_filter = e; + *last_data_write = std::chrono::system_clock::now(); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + } + ImGui::EndCombo(); + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("U Wrap Mode"); + ImGui::TableNextColumn(); + if (ImGui::BeginCombo("##UWrapMode", + fmt::to_string(texture_data->m_sampler.tm0.wrap_u).c_str())) + { + for (auto e = WrapMode::Clamp; e <= WrapMode::Mirror; + e = static_cast(static_cast(e) + 1)) + { + const bool is_selected = texture_data->m_sampler.tm0.wrap_u == e; + if (ImGui::Selectable(fmt::to_string(e).c_str(), is_selected)) + { + texture_data->m_sampler.tm0.wrap_u = e; + *last_data_write = std::chrono::system_clock::now(); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + } + ImGui::EndCombo(); + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("V Wrap Mode"); + ImGui::TableNextColumn(); + if (ImGui::BeginCombo("##VWrapMode", + fmt::to_string(texture_data->m_sampler.tm0.wrap_v).c_str())) + { + for (auto e = WrapMode::Clamp; e <= WrapMode::Mirror; + e = static_cast(static_cast(e) + 1)) + { + const bool is_selected = texture_data->m_sampler.tm0.wrap_v == e; + if (ImGui::Selectable(fmt::to_string(e).c_str(), is_selected)) + { + texture_data->m_sampler.tm0.wrap_v = e; + *last_data_write = std::chrono::system_clock::now(); + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(asset_id); + } + } + ImGui::EndCombo(); + } + ImGui::EndTable(); + + if (texture_preview) + { + const auto ratio = texture_preview->GetWidth() / texture_preview->GetHeight(); + ImGui::Image(*texture_preview, ImVec2{ImGui::GetContentRegionAvail().x, + ImGui::GetContentRegionAvail().x * ratio}); + } + } +} +} // namespace GraphicsModEditor::Controls diff --git a/Source/Core/VideoCommon/GraphicsModEditor/Controls/TextureControl.h b/Source/Core/VideoCommon/GraphicsModEditor/Controls/TextureControl.h new file mode 100644 index 0000000000..788d790d79 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/Controls/TextureControl.h @@ -0,0 +1,31 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "VideoCommon/Assets/CustomAsset.h" +#include "VideoCommon/GraphicsModEditor/EditorState.h" + +namespace VideoCommon +{ +class AbstractTexture; +struct TextureData; +} // namespace VideoCommon + +namespace GraphicsModEditor::Controls +{ +class TextureControl +{ +public: + explicit TextureControl(EditorState& state); + void DrawImGui(const VideoCommon::CustomAssetLibrary::AssetID& asset_id, + VideoCommon::TextureData* texture_data, const std::filesystem::path& path, + VideoCommon::CustomAssetLibrary::TimeType* last_data_write, + AbstractTexture* texture_preview); + +private: + EditorState& m_state; +}; +} // namespace GraphicsModEditor::Controls diff --git a/Source/Core/VideoCommon/GraphicsModEditor/EditorAssetSource.cpp b/Source/Core/VideoCommon/GraphicsModEditor/EditorAssetSource.cpp new file mode 100644 index 0000000000..acf1ee175d --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/EditorAssetSource.cpp @@ -0,0 +1,895 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModEditor/EditorAssetSource.h" + +#include +#include + +#include +#include +#include + +#include "Common/FileUtil.h" +#include "Common/IOFile.h" +#include "Common/JsonUtil.h" +#include "Common/StringUtil.h" +#include "Common/VariantUtil.h" +#include "Core/System.h" +#include "VideoCommon/AbstractGfx.h" +#include "VideoCommon/Assets/CustomAssetLoader.h" +#include "VideoCommon/Assets/CustomTextureData.h" +#include "VideoCommon/GraphicsModSystem/Runtime/CustomResourceManager.h" + +namespace +{ +std::size_t GetAssetSize(const VideoCommon::CustomTextureData& data) +{ + std::size_t total = 0; + for (const auto& slice : data.m_slices) + { + for (const auto& level : slice.m_levels) + { + total += level.data.size(); + } + } + return total; +} + +std::chrono::system_clock::time_point FileTimeToSysTime(std::filesystem::file_time_type file_time) +{ + // Note: all compilers should switch to chrono::clock_cast + // once it is available for use + const auto system_time_now = std::chrono::system_clock::now(); + const auto file_time_now = decltype(file_time)::clock::now(); + return std::chrono::time_point_cast( + file_time - file_time_now + system_time_now); +} + +std::optional GetJsonObjectFromFile(const std::filesystem::path& path) +{ + picojson::value root; + std::string error; + if (!JsonFromFile(PathToString(path), &root, &error)) + { + ERROR_LOG_FMT(VIDEO, "Json file at path '{}' has error '{}'!", PathToString(path), error); + return std::nullopt; + } + + if (!root.is()) + { + return std::nullopt; + } + + return root.get(); +} +} // namespace + +namespace GraphicsModEditor +{ +EditorAssetSource::LoadInfo EditorAssetSource::LoadTexture(const AssetID& asset_id, + VideoCommon::TextureData* data) +{ + std::filesystem::path texture_path; + { + std::lock_guard lk(m_asset_lock); + const auto asset = GetAssetFromID(asset_id); + if (!asset) + { + ERROR_LOG_FMT(VIDEO, "Asset with id '{}' not found!", asset_id); + return {}; + } + + if (const auto it = asset->m_asset_map.find("texture"); it != asset->m_asset_map.end()) + { + texture_path = it->second; + } + else + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' error - could not find 'texture' in asset map!", asset_id); + return {}; + } + if (auto texture_data = std::get_if>(&asset->m_data)) + { + data->m_sampler = texture_data->get()->m_sampler; + data->m_type = texture_data->get()->m_type; + } + } + + auto ext = PathToString(texture_path.extension()); + Common::ToLower(&ext); + if (ext == ".dds") + { + if (!LoadDDSTexture(&data->m_texture, PathToString(texture_path))) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' error - could not load dds texture!", asset_id); + return {}; + } + + // SetAssetPreviewData(asset_id, data->m_texture); + return LoadInfo{GetAssetSize(data->m_texture), GetLastAssetWriteTime(asset_id)}; + } + else if (ext == ".png") + { + // PNG could support more complicated texture types in the future + // but for now just error + if (data->m_type != VideoCommon::TextureData::Type::Type_Texture2D) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' error - PNG is not supported for texture type '{}'!", + asset_id, data->m_type); + return {}; + } + + // If we have no slices, create one + if (data->m_texture.m_slices.empty()) + data->m_texture.m_slices.push_back({}); + + auto& slice = data->m_texture.m_slices[0]; + // If we have no levels, create one to pass into LoadPNGTexture + if (slice.m_levels.empty()) + slice.m_levels.push_back({}); + + if (!LoadPNGTexture(&slice.m_levels[0], PathToString(texture_path))) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' error - could not load png texture!", asset_id); + return {}; + } + + // SetAssetPreviewData(asset_id, data->m_texture); + return LoadInfo{GetAssetSize(data->m_texture), GetLastAssetWriteTime(asset_id)}; + } + + return {}; +} + +EditorAssetSource::LoadInfo EditorAssetSource::LoadPixelShader(const AssetID& asset_id, + VideoCommon::PixelShaderData* data) +{ + std::lock_guard lk(m_asset_lock); + auto asset = GetAssetFromID(asset_id); + if (asset) + { + if (auto pixel_shader_data = + std::get_if>(&asset->m_data)) + { + if (const auto it = asset->m_asset_map.find("shader"); it != asset->m_asset_map.end()) + { + if (File::ReadFileToString(PathToString(it->second), + pixel_shader_data->get()->m_shader_source)) + { + *data = *pixel_shader_data->get(); + return LoadInfo{sizeof(VideoCommon::PixelShaderData), GetLastAssetWriteTime(asset_id)}; + } + } + } + } + + return {}; +} + +EditorAssetSource::LoadInfo EditorAssetSource::LoadShader(const AssetID& asset_id, + VideoCommon::RasterShaderData* data) +{ + std::lock_guard lk(m_asset_lock); + auto asset = GetAssetFromID(asset_id); + if (asset) + { + if (auto shader_data = + std::get_if>(&asset->m_data)) + { + if (const auto it = asset->m_asset_map.find("vertex_shader"); it != asset->m_asset_map.end()) + { + if (!File::ReadFileToString(PathToString(it->second), shader_data->get()->m_vertex_source)) + { + return {}; + } + } + + if (const auto it = asset->m_asset_map.find("pixel_shader"); it != asset->m_asset_map.end()) + { + if (!File::ReadFileToString(PathToString(it->second), shader_data->get()->m_pixel_source)) + { + return {}; + } + } + + if (shader_data->get()->m_vertex_source != "" || shader_data->get()->m_pixel_source != "") + { + *data = *shader_data->get(); + return LoadInfo{sizeof(VideoCommon::RasterShaderData), GetLastAssetWriteTime(asset_id)}; + } + } + } + + return {}; +} + +EditorAssetSource::LoadInfo EditorAssetSource::LoadMaterial(const AssetID& asset_id, + VideoCommon::MaterialData* data) +{ + std::lock_guard lk(m_asset_lock); + auto asset = GetAssetFromID(asset_id); + if (asset) + { + if (auto material_data = + std::get_if>(&asset->m_data)) + { + *data = *material_data->get(); + return LoadInfo{sizeof(VideoCommon::MaterialData), asset->m_last_data_write}; + } + } + + return {}; +} + +EditorAssetSource::LoadInfo EditorAssetSource::LoadMaterial(const AssetID& asset_id, + VideoCommon::RasterMaterialData* data) +{ + std::lock_guard lk(m_asset_lock); + auto asset = GetAssetFromID(asset_id); + if (asset) + { + if (auto material_data = + std::get_if>(&asset->m_data)) + { + *data = *material_data->get(); + return LoadInfo{sizeof(VideoCommon::RasterMaterialData), asset->m_last_data_write}; + } + } + + return {}; +} + +EditorAssetSource::LoadInfo EditorAssetSource::LoadMesh(const AssetID& asset_id, + VideoCommon::MeshData* data) +{ + std::filesystem::path mesh_path; + { + std::lock_guard lk(m_asset_lock); + const auto asset = GetAssetFromID(asset_id); + if (!asset) + { + ERROR_LOG_FMT(VIDEO, "Asset with id '{}' not found!", asset_id); + return {}; + } + + if (const auto it = asset->m_asset_map.find("mesh"); it != asset->m_asset_map.end()) + { + mesh_path = it->second; + } + else + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' error - could not find 'mesh' in asset map!", asset_id); + return {}; + } + if (auto mesh_data = std::get_if>(&asset->m_data)) + { + data->m_mesh_material_to_material_asset_id = + mesh_data->get()->m_mesh_material_to_material_asset_id; + } + } + + auto ext = PathToString(mesh_path.extension()); + Common::ToLower(&ext); + if (ext == ".dolmesh") + { + File::IOFile file(PathToString(mesh_path), "rb"); + std::vector bytes; + bytes.reserve(file.GetSize()); + file.ReadBytes(bytes.data(), file.GetSize()); + if (!VideoCommon::MeshData::FromDolphinMesh(bytes, data)) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' error - failed to load the mesh file '{}',", asset_id, + PathToString(mesh_path)); + return {}; + } + + // SetAssetPreviewData(asset_id, data->m_texture); + return LoadInfo{1, GetLastAssetWriteTime(asset_id)}; + } + + return {}; +} + +EditorAssetSource::LoadInfo EditorAssetSource::LoadRenderTarget(const AssetID& asset_id, + VideoCommon::RenderTargetData* data) +{ + std::lock_guard lk(m_asset_lock); + auto asset = GetAssetFromID(asset_id); + if (asset) + { + if (auto render_target_data = + std::get_if>(&asset->m_data)) + { + *data = *render_target_data->get(); + return LoadInfo{sizeof(VideoCommon::RenderTargetData), asset->m_last_data_write}; + } + } + + return {}; +} + +EditorAssetSource::TimeType EditorAssetSource::GetLastAssetWriteTime(const AssetID& asset_id) const +{ + std::lock_guard lk(m_asset_lock); + auto asset = GetAssetFromID(asset_id); + if (asset) + { + CustomAssetLibrary::TimeType max_entry = asset->m_last_data_write; + for (const auto& [name, path] : asset->m_asset_map) + { + std::error_code ec; + const auto tp = std::filesystem::last_write_time(path, ec); + if (ec) + continue; + + auto tp_sys = FileTimeToSysTime(tp); + if (tp_sys > max_entry) + max_entry = tp_sys; + } + return max_entry; + } + + return {}; +} + +EditorAsset* EditorAssetSource::GetAssetFromPath(const std::filesystem::path& asset_path) +{ + std::lock_guard lk(m_asset_lock); + const auto it = m_path_to_editor_asset.find(asset_path); + if (it == m_path_to_editor_asset.end()) + return nullptr; + + return &(*it->second); +} + +const EditorAsset* EditorAssetSource::GetAssetFromID(const AssetID& asset_id) const +{ + std::lock_guard lk(m_asset_lock); + + const auto asset_it = m_asset_id_to_file_path.find(asset_id); + if (asset_it == m_asset_id_to_file_path.end()) + return nullptr; + + const auto it = m_path_to_editor_asset.find(asset_it->second); + if (it == m_path_to_editor_asset.end()) + return nullptr; + + return &(*it->second); +} + +EditorAsset* EditorAssetSource::GetAssetFromID(const AssetID& asset_id) +{ + std::lock_guard lk(m_asset_lock); + + const auto asset_it = m_asset_id_to_file_path.find(asset_id); + if (asset_it == m_asset_id_to_file_path.end()) + return nullptr; + + const auto it = m_path_to_editor_asset.find(asset_it->second); + if (it == m_path_to_editor_asset.end()) + return nullptr; + + return &(*it->second); +} + +void EditorAssetSource::AddAsset(const std::filesystem::path& asset_path) +{ + std::string uuid_str = + fmt::format("{}", std::chrono::system_clock::now().time_since_epoch().count()); + AddAsset(asset_path, std::move(uuid_str)); +} + +void EditorAssetSource::AddAsset(const std::filesystem::path& asset_path, AssetID uuid) +{ + std::lock_guard lk(m_asset_lock); + EditorAsset asset; + asset.m_valid = true; + bool add = false; + const std::string filename = PathToString(asset_path.filename()); + auto ext = PathToString(asset_path.extension()); + Common::ToLower(&ext); + if (ext == ".dds" || ext == ".png") + { + auto texture_data = std::make_unique(); + + // Metadata is optional and will be created + // if it doesn't exist + const auto root = asset_path.parent_path(); + const auto name = asset_path.stem(); + const auto extension = asset_path.extension(); + auto result = root / name; + result += ".texture"; + if (std::filesystem::exists(result)) + { + if (auto json = GetJsonObjectFromFile(result)) + { + VideoCommon::TextureData::FromJson(uuid, *json, texture_data.get()); + } + } + else + { + texture_data->m_type = VideoCommon::TextureData::Type::Type_Texture2D; + + // Write initial data to a file, so that if any changes are made + // they get picked up and written on save + picojson::object obj; + VideoCommon::TextureData::ToJson(&obj, *texture_data); + JsonToFile(PathToString(result), picojson::value{obj}, true); + } + asset.m_asset_map["metadata"] = result; + + add = true; + asset.m_data = std::move(texture_data); + asset.m_data_type = Texture; + asset.m_asset_map["texture"] = asset_path; + } + else if (ext == ".glsl") + { + // Only valid if metadata file exists + const auto root = asset_path.parent_path(); + const auto name = asset_path.stem(); + const auto extension = asset_path.extension(); + auto result = root / name; + result += ".shader"; + if (std::filesystem::exists(result)) + { + if (auto json = GetJsonObjectFromFile(result)) + { + auto pixel_data = std::make_unique(); + if (File::ReadFileToString(PathToString(asset_path), pixel_data->m_shader_source)) + { + if (VideoCommon::PixelShaderData::FromJson(uuid, *json, pixel_data.get())) + { + add = true; + asset.m_data = std::move(pixel_data); + asset.m_data_type = PixelShader; + asset.m_asset_map["shader"] = asset_path; + asset.m_asset_map["metadata"] = result; + } + } + } + } + } + else if (ext == ".rastershader") + { + // Either the vertex or pixel shaders may exist + const auto root = asset_path.parent_path(); + const auto name = asset_path.stem(); + const auto extension = asset_path.extension(); + const auto pixel_shader = (root / name).replace_extension(".ps.glsl"); + const auto vertex_shader = (root / name).replace_extension(".vs.glsl"); + + if (auto json = GetJsonObjectFromFile(asset_path)) + { + auto shader_data = std::make_unique(); + + bool uses_custom_shader = false; + + if (File::ReadFileToString(PathToString(pixel_shader), shader_data->m_pixel_source)) + { + asset.m_asset_map["pixel_shader"] = pixel_shader; + uses_custom_shader = true; + } + + if (File::ReadFileToString(PathToString(vertex_shader), shader_data->m_vertex_source)) + { + asset.m_asset_map["vertex_shader"] = vertex_shader; + uses_custom_shader = true; + } + + if (uses_custom_shader && + VideoCommon::RasterShaderData::FromJson(uuid, *json, shader_data.get())) + { + asset.m_data = std::move(shader_data); + asset.m_data_type = Shader; + asset.m_asset_map["metadata"] = asset_path; + add = true; + } + } + } + else if (ext == ".dolmesh") + { + auto mesh_data = std::make_unique(); + + // Only valid if metadata file exists + const auto root = asset_path.parent_path(); + const auto name = asset_path.stem(); + const auto extension = asset_path.extension(); + auto result = root / name; + result += ".metadata"; + if (std::filesystem::exists(result)) + { + if (auto json = GetJsonObjectFromFile(result)) + { + VideoCommon::MeshData::FromJson(uuid, *json, mesh_data.get()); + add = true; + asset.m_data = std::move(mesh_data); + asset.m_data_type = Mesh; + asset.m_asset_map["mesh"] = asset_path; + } + asset.m_asset_map["metadata"] = result; + } + } + else if (ext == ".material") + { + if (auto json = GetJsonObjectFromFile(asset_path)) + { + auto material_data = std::make_unique(); + if (VideoCommon::MaterialData::FromJson(uuid, *json, material_data.get())) + { + add = true; + asset.m_data = std::move(material_data); + asset.m_data_type = Material; + asset.m_asset_map["metadata"] = asset_path; + } + } + } + else if (ext == ".rastermaterial") + { + if (auto json = GetJsonObjectFromFile(asset_path)) + { + auto material_data = std::make_unique(); + if (VideoCommon::RasterMaterialData::FromJson(uuid, *json, material_data.get())) + { + add = true; + asset.m_data = std::move(material_data); + asset.m_data_type = RasterMaterial; + asset.m_asset_map["metadata"] = asset_path; + } + } + } + else if (ext == ".rendertarget") + { + if (auto json = GetJsonObjectFromFile(asset_path)) + { + auto render_target_data = std::make_unique(); + if (VideoCommon::RenderTargetData::FromJson(uuid, *json, render_target_data.get())) + { + add = true; + asset.m_data = std::move(render_target_data); + asset.m_data_type = RenderTarget; + asset.m_asset_map["metadata"] = asset_path; + } + } + } + + if (add) + { + asset.m_asset_id = uuid; + asset.m_asset_path = asset_path; + asset.m_last_data_write = std::chrono::system_clock::now(); + m_assets.emplace_back(std::move(asset)); + for (const auto& [name, path] : m_assets.back().m_asset_map) + { + m_path_to_editor_asset.insert_or_assign(path, std::prev(m_assets.end())); + } + m_asset_id_to_file_path.insert_or_assign(uuid, asset_path); + } +} + +void EditorAssetSource::RemoveAsset(const std::filesystem::path& asset_path) +{ + std::lock_guard lk(m_asset_lock); + if (const auto it = m_path_to_editor_asset.find(asset_path); it != m_path_to_editor_asset.end()) + { + const auto& asset_it = it->second; + m_asset_id_to_file_path.erase(asset_it->m_asset_id); + + // Make a copy as we are going to remove the found iterator + const auto asset_map = asset_it->m_asset_map; + for (const auto& [name, path] : asset_map) + { + m_path_to_editor_asset.erase(path); + } + + m_assets.erase(asset_it); + } +} + +bool EditorAssetSource::RenameAsset(const std::filesystem::path& old_path, + const std::filesystem::path& new_path) +{ + // If old path and new path are the same + // abort the rename + if (old_path == new_path) + return true; + + if (std::filesystem::exists(new_path)) + return false; + + std::lock_guard lk(m_asset_lock); + const auto it = m_path_to_editor_asset.find(old_path); + if (it == m_path_to_editor_asset.end()) + return false; + + it->second->m_asset_path = new_path; + + auto ext = PathToString(old_path.extension()); + Common::ToLower(&ext); + if (ext == ".dds" || ext == ".png") + { + const auto old_root = old_path.parent_path(); + const auto old_name = old_path.stem(); + auto old_result = old_root / old_name; + old_result += ".texture"; + if (std::filesystem::exists(old_result)) + { + const auto new_root = new_path.parent_path(); + const auto new_name = new_path.stem(); + auto new_result = new_root / new_name; + new_result += ".texture"; + std::filesystem::rename(old_result, new_result); + + it->second->m_asset_map["metadata"] = new_result; + } + it->second->m_asset_map["texture"] = new_path; + } + else if (ext == ".gltf") + { + const auto old_root = old_path.parent_path(); + const auto old_name = old_path.stem(); + auto old_result = old_root / old_name; + old_result += ".metadata"; + if (std::filesystem::exists(old_result)) + { + const auto new_root = new_path.parent_path(); + const auto new_name = new_path.stem(); + auto new_result = new_root / new_name; + new_result += ".metadata"; + std::filesystem::rename(old_result, new_result); + + it->second->m_asset_map["metadata"] = new_result; + } + it->second->m_asset_map["mesh"] = new_path; + } + else if (ext == ".rastershader") + { + it->second->m_asset_map["metadata"] = new_path; + + const auto old_root = old_path.parent_path(); + const auto old_name = old_path.stem(); + const auto old_pixel_shader_path = (old_root / old_name).replace_extension(".ps.glsl"); + if (std::filesystem::exists(old_pixel_shader_path)) + { + const auto new_root = new_path.parent_path(); + const auto new_name = new_path.stem(); + const auto new_pixel_shader_path = (new_root / new_name).replace_extension(".ps.glsl"); + std::filesystem::rename(old_pixel_shader_path, new_pixel_shader_path); + + it->second->m_asset_map["pixel_shader"] = new_pixel_shader_path; + } + const auto old_vertex_shader_path = (old_root / old_name).replace_extension(".vs.glsl"); + if (std::filesystem::exists(old_vertex_shader_path)) + { + const auto new_root = new_path.parent_path(); + const auto new_name = new_path.stem(); + const auto new_vertex_shader_path = (new_root / new_name).replace_extension(".vs.glsl"); + std::filesystem::rename(old_vertex_shader_path, new_vertex_shader_path); + + it->second->m_asset_map["vertex_shader"] = new_vertex_shader_path; + } + } + else if (ext == ".material") + { + it->second->m_asset_map["metadata"] = new_path; + } + else if (ext == ".rastermaterial") + { + it->second->m_asset_map["metadata"] = new_path; + } + else if (ext == ".rendertarget") + { + it->second->m_asset_map["metadata"] = new_path; + } + m_asset_id_to_file_path[it->second->m_asset_id] = new_path; + + for (const auto [name, path] : it->second->m_asset_map) + { + m_path_to_editor_asset.erase(path); + } + std::filesystem::rename(old_path, new_path); + return true; +} + +void EditorAssetSource::AddAssets( + std::span assets, + const std::filesystem::path& root) +{ + for (const auto& asset : assets) + { + for (const auto& [name, path] : asset.m_map) + { + AddAsset(root / path, asset.m_asset_id); + } + } +} + +std::vector +EditorAssetSource::GetAssets(const std::filesystem::path& root) const +{ + std::lock_guard lk(m_asset_lock); + std::vector assets; + for (const auto& [asset_id, path] : m_asset_id_to_file_path) + { + GraphicsModSystem::Config::GraphicsModAsset asset_config; + asset_config.m_asset_id = asset_id; + if (const auto it = m_path_to_editor_asset.find(path); it != m_path_to_editor_asset.end()) + { + for (const auto& [name, am_path] : it->second->m_asset_map) + { + asset_config.m_map[name] = std::filesystem::relative(am_path, root); + } + } + assets.push_back(std::move(asset_config)); + } + return assets; +} + +void EditorAssetSource::SaveAssetDataAsFiles() const +{ + const auto save_json_to_file = [](const std::string& file_path, + const picojson::object& serialized_root) { + std::ofstream json_stream; + File::OpenFStream(json_stream, file_path, std::ios_base::out); + if (!json_stream.is_open()) + { + ERROR_LOG_FMT(VIDEO, "Failed to open file '{}' for writing", file_path); + return; + } + + const auto output = picojson::value{serialized_root}.serialize(true); + json_stream << output; + }; + std::lock_guard lk(m_asset_lock); + for (const auto& pair : m_path_to_editor_asset) + { + // Workaround for some compilers not being able + // to capture structured bindings in lambda + const auto& asset = *pair.second; + std::visit( + overloaded{[&](const std::unique_ptr& material_data) { + if (const auto metadata_it = asset.m_asset_map.find("metadata"); + metadata_it != asset.m_asset_map.end()) + { + picojson::object serialized_root; + VideoCommon::MaterialData::ToJson(&serialized_root, *material_data); + save_json_to_file(PathToString(metadata_it->second), serialized_root); + } + }, + [&](const std::unique_ptr& material_data) { + if (const auto metadata_it = asset.m_asset_map.find("metadata"); + metadata_it != asset.m_asset_map.end()) + { + picojson::object serialized_root; + VideoCommon::RasterMaterialData::ToJson(&serialized_root, *material_data); + save_json_to_file(PathToString(metadata_it->second), serialized_root); + } + }, + [&](const std::unique_ptr& pixel_shader_data) { + if (const auto metadata_it = asset.m_asset_map.find("metadata"); + metadata_it != asset.m_asset_map.end()) + { + picojson::object serialized_root; + VideoCommon::PixelShaderData::ToJson(serialized_root, *pixel_shader_data); + save_json_to_file(PathToString(metadata_it->second), serialized_root); + } + }, + [&](const std::unique_ptr& shader_data) { + if (const auto metadata_it = asset.m_asset_map.find("metadata"); + metadata_it != asset.m_asset_map.end()) + { + picojson::object serialized_root; + VideoCommon::RasterShaderData::ToJson(serialized_root, *shader_data); + save_json_to_file(PathToString(metadata_it->second), serialized_root); + } + }, + [&](const std::unique_ptr& texture_data) { + if (const auto metadata_it = asset.m_asset_map.find("metadata"); + metadata_it != asset.m_asset_map.end()) + { + picojson::object serialized_root; + VideoCommon::TextureData::ToJson(&serialized_root, *texture_data); + save_json_to_file(PathToString(metadata_it->second), serialized_root); + } + }, + [&](const std::unique_ptr& mesh_data) { + if (const auto metadata_it = asset.m_asset_map.find("metadata"); + metadata_it != asset.m_asset_map.end()) + { + picojson::object serialized_root; + VideoCommon::MeshData::ToJson(serialized_root, *mesh_data); + save_json_to_file(PathToString(metadata_it->second), serialized_root); + } + }, + [&](const std::unique_ptr& render_target_data) { + if (const auto metadata_it = asset.m_asset_map.find("metadata"); + metadata_it != asset.m_asset_map.end()) + { + picojson::object serialized_root; + VideoCommon::RenderTargetData::ToJson(&serialized_root, *render_target_data); + save_json_to_file(PathToString(metadata_it->second), serialized_root); + } + }}, + asset.m_data); + } +} + +const std::list& EditorAssetSource::GetAllAssets() const +{ + std::lock_guard lk(m_asset_lock); + return m_assets; +} + +void EditorAssetSource::PathAdded(std::string_view path) +{ + // TODO: notify our preview that it needs to be calculated +} + +void EditorAssetSource::PathModified(std::string_view path) +{ + std::lock_guard lk(m_asset_lock); + if (const auto iter = m_path_to_editor_asset.find(path); iter != m_path_to_editor_asset.end()) + { + auto& system = Core::System::GetInstance(); + auto& custom_resource_manager = system.GetCustomResourceManager(); + custom_resource_manager.ReloadAsset(iter->second->m_asset_id); + // auto& loader = system.GetCustomAssetLoader(); + // loader.ReloadAsset(iter->second->m_asset_id); + } + + // TODO: notify our preview that something changed +} + +void EditorAssetSource::PathRenamed(std::string_view old_path, std::string_view new_path) +{ + // TODO: notify our preview that our path changed +} + +void EditorAssetSource::PathDeleted(std::string_view path) +{ + // TODO: notify our preview that we no longer care about this path +} + +AbstractTexture* EditorAssetSource::GetAssetPreview(const AssetID& asset_id) +{ + std::lock_guard lk(m_preview_lock); + if (const auto it = m_asset_id_to_preview.find(asset_id); it != m_asset_id_to_preview.end()) + { + if (it->second.m_preview_data) + { + if (it->second.m_preview_data->m_slices.empty()) [[unlikely]] + return nullptr; + + auto& first_slice = it->second.m_preview_data->m_slices[0]; + if (first_slice.m_levels.empty()) [[unlikely]] + return nullptr; + + auto& first_level = first_slice.m_levels[0]; + it->second.m_preview_texture = g_gfx->CreateTexture( + TextureConfig{first_level.width, first_level.height, 1, 1, 1, + AbstractTextureFormat::RGBA8, 0, AbstractTextureType::Texture_2DArray}); + + it->second.m_preview_texture->Load(0, first_level.width, first_level.height, + first_level.row_length, first_level.data.data(), + first_level.data.size()); + + it->second.m_preview_data.reset(); + } + + return it->second.m_preview_texture.get(); + } + + return nullptr; +} + +void EditorAssetSource::SetAssetPreviewData(const AssetID& asset_id, + const VideoCommon::CustomTextureData& preview_data) +{ + std::lock_guard lk(m_preview_lock); + const auto [it, inserted] = m_asset_id_to_preview.try_emplace( + asset_id, AssetPreview{.m_preview_data = {}, .m_preview_texture = nullptr}); + it->second.m_preview_data = preview_data; +} + +} // namespace GraphicsModEditor diff --git a/Source/Core/VideoCommon/GraphicsModEditor/EditorAssetSource.h b/Source/Core/VideoCommon/GraphicsModEditor/EditorAssetSource.h new file mode 100644 index 0000000000..ef4a0cb5c1 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/EditorAssetSource.h @@ -0,0 +1,93 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "VideoCommon/Assets/MaterialAsset.h" +#include "VideoCommon/Assets/MeshAsset.h" +#include "VideoCommon/Assets/RenderTargetAsset.h" +#include "VideoCommon/Assets/ShaderAsset.h" +#include "VideoCommon/Assets/TextureAsset.h" +#include "VideoCommon/Assets/WatchableFilesystemAssetLibrary.h" +#include "VideoCommon/GraphicsModEditor/EditorTypes.h" +#include "VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.h" + +class AbstractTexture; +namespace VideoCommon +{ +class CustomAsset; +class CustomTextureData; +} // namespace VideoCommon + +namespace GraphicsModEditor +{ +class EditorAssetSource final : public VideoCommon::WatchableFilesystemAssetLibrary +{ +public: + // CustomAssetLibrary interface + LoadInfo LoadTexture(const AssetID& asset_id, VideoCommon::TextureData* data) override; + LoadInfo LoadPixelShader(const AssetID& asset_id, VideoCommon::PixelShaderData* data) override; + LoadInfo LoadShader(const AssetID& asset_id, VideoCommon::RasterShaderData* data) override; + LoadInfo LoadMaterial(const AssetID& asset_id, VideoCommon::MaterialData* data) override; + LoadInfo LoadMaterial(const AssetID& asset_id, VideoCommon::RasterMaterialData* data) override; + LoadInfo LoadMesh(const AssetID& asset_id, VideoCommon::MeshData* data) override; + LoadInfo LoadRenderTarget(const AssetID& asset_id, VideoCommon::RenderTargetData* data) override; + TimeType GetLastAssetWriteTime(const AssetID& asset_id) const override; + + // Editor interface + EditorAsset* GetAssetFromPath(const std::filesystem::path& asset_path); + const EditorAsset* GetAssetFromID(const AssetID& asset_id) const; + EditorAsset* GetAssetFromID(const AssetID& asset_id); + + void AddAsset(const std::filesystem::path& asset_path); + void RemoveAsset(const std::filesystem::path& asset_path); + bool RenameAsset(const std::filesystem::path& old_path, const std::filesystem::path& new_path); + + void AddAssets(std::span assets, + const std::filesystem::path& root); + std::vector + GetAssets(const std::filesystem::path& root) const; + void SaveAssetDataAsFiles() const; + + const std::list& GetAllAssets() const; + + // Gets a texture associated with the asset preview + // Will create a new texture if data is available + AbstractTexture* GetAssetPreview(const AssetID& asset_id); + +private: + void PathAdded(std::string_view path) override; + void PathModified(std::string_view path) override; + void PathRenamed(std::string_view old_path, std::string_view new_path) override; + void PathDeleted(std::string_view path) override; + + void AddAsset(const std::filesystem::path& asset_path, AssetID uuid); + void SetAssetPreviewData(const AssetID& asset_id, + const VideoCommon::CustomTextureData& preview_data); + + std::map m_asset_id_to_file_path; + std::map::iterator> m_path_to_editor_asset; + std::list m_assets; + mutable std::recursive_mutex m_asset_lock; + + struct AssetPreview + { + std::optional m_preview_data; + std::unique_ptr m_preview_texture; + }; + std::map m_asset_id_to_preview; + mutable std::mutex m_preview_lock; +}; +} // namespace GraphicsModEditor diff --git a/Source/Core/VideoCommon/GraphicsModEditor/EditorBackend.cpp b/Source/Core/VideoCommon/GraphicsModEditor/EditorBackend.cpp new file mode 100644 index 0000000000..cc9bc504f2 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/EditorBackend.cpp @@ -0,0 +1,354 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModEditor/EditorBackend.h" + +#include +#include + +#include "Common/EnumUtils.h" +#include "Common/FileUtil.h" +#include "Core/ConfigManager.h" +#include "Core/System.h" + +#include "VideoCommon/BPMemory.h" +#include "VideoCommon/CPMemory.h" +#include "VideoCommon/GraphicsModEditor/EditorEvents.h" +#include "VideoCommon/GraphicsModEditor/MaterialGeneration.h" +#include "VideoCommon/GraphicsModEditor/ShaderGeneration.h" +#include "VideoCommon/GraphicsModSystem/Runtime/CustomResourceManager.h" +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModHash.h" +#include "VideoCommon/GraphicsModSystem/Types.h" +#include "VideoCommon/PixelShaderGen.h" +#include "VideoCommon/ShaderGenCommon.h" +#include "VideoCommon/VertexLoaderManager.h" +#include "VideoCommon/VertexShaderGen.h" +#include "VideoCommon/VideoConfig.h" +#include "VideoCommon/XFMemory.h" + +namespace GraphicsModEditor +{ +EditorBackend::EditorBackend(EditorState& state) : m_state(state) +{ + m_selection_event = EditorEvents::ItemsSelectedEvent::Register( + [this](const auto& selected_targets) { SelectionOccurred(selected_targets); }, + "EditorBackendSelect"); +} + +void EditorBackend::OnDraw(const GraphicsModSystem::DrawDataView& draw_data, + VertexManagerBase* vertex_manager) +{ + const auto hash_output = + GraphicsModSystem::Runtime::GetDrawDataHash(m_state.m_user_data.m_hash_policy, draw_data); + + const GraphicsModSystem::DrawCallID draw_call_id = + GetSkinnedDrawCallID(hash_output.draw_call_id, hash_output.material_id, draw_data); + + if (m_state.m_scene_dumper.IsRecording()) + { + if (m_state.m_scene_dumper.IsDrawCallInRecording(draw_call_id)) + { + GraphicsModEditor::SceneDumper::AdditionalDrawData additional_draw_data; + if (!draw_data.vertex_format->GetVertexDeclaration().posmtx.enable) + { + float* pos = &xfmem.posMatrices[g_main_cp_state.matrix_index_a.PosNormalMtxIdx * 4]; + additional_draw_data.transform = {pos, 12}; + } + m_state.m_scene_dumper.AddDataToRecording(hash_output.draw_call_id, draw_data, + std::move(additional_draw_data)); + } + } + + if (draw_call_id == hash_output.draw_call_id) + { + const auto now = std::chrono::steady_clock::now(); + if (auto iter = m_state.m_runtime_data.m_draw_call_id_to_data.find(hash_output.draw_call_id); + iter != m_state.m_runtime_data.m_draw_call_id_to_data.end()) + { + if (m_xfb_counter > (iter->second.draw_data.xfb_counter + 1)) + { + iter->second.m_create_time = now; + } + iter->second.draw_data.blending_state = draw_data.uid->blending_state; + iter->second.draw_data.depth_state = draw_data.uid->depth_state; + iter->second.draw_data.projection_type = draw_data.projection_type; + iter->second.draw_data.projection_mat = xfmem.projection.rawProjection; + iter->second.draw_data.rasterization_state = draw_data.uid->rasterization_state; + iter->second.draw_data.xfb_counter = m_xfb_counter; + iter->second.draw_data.vertex_count = draw_data.vertex_data.size(); + iter->second.draw_data.index_count = draw_data.index_data.size(); + + Common::SmallVector textures; + for (const auto& texture_view : draw_data.textures) + { + GraphicsModSystem::Texture texture; + texture.hash_name = std::string{texture_view.hash_name}; + texture.texture_type = texture_view.texture_type; + texture.unit = texture_view.unit; + textures.push_back(std::move(texture)); + } + iter->second.draw_data.textures = textures; + iter->second.draw_data.samplers = draw_data.samplers; + RuntimeState::XFBData::DrawCallWithTime draw_call_with_time{hash_output.draw_call_id, + iter->second.m_create_time}; + m_state.m_runtime_data.m_current_xfb.m_draw_call_ids.insert(draw_call_with_time); + iter->second.m_last_update_time = now; + } + else + { + GraphicsModEditor::DrawCallData data; + data.m_id = hash_output.draw_call_id; + data.draw_data.blending_state = draw_data.uid->blending_state; + data.draw_data.depth_state = draw_data.uid->depth_state; + data.draw_data.projection_type = draw_data.projection_type; + data.draw_data.projection_mat = xfmem.projection.rawProjection; + data.draw_data.rasterization_state = draw_data.uid->rasterization_state; + data.draw_data.xfb_counter = m_xfb_counter; + + Common::SmallVector textures; + for (const auto& texture_view : draw_data.textures) + { + GraphicsModSystem::Texture texture; + texture.hash_name = std::string{texture_view.hash_name}; + texture.texture_type = texture_view.texture_type; + texture.unit = texture_view.unit; + textures.push_back(std::move(texture)); + } + data.draw_data.textures = textures; + data.draw_data.samplers = draw_data.samplers; + + data.m_create_time = now; + data.m_last_update_time = now; + m_state.m_runtime_data.m_draw_call_id_to_data.try_emplace(hash_output.draw_call_id, + std::move(data)); + RuntimeState::XFBData::DrawCallWithTime draw_call_with_time{hash_output.draw_call_id, now}; + m_state.m_runtime_data.m_current_xfb.m_draw_call_ids.insert(draw_call_with_time); + } + } + + if (m_state.m_editor_data.m_export_dolphin_material_draw_call == draw_call_id) + { + const std::string path = File::GetUserPath(D_DUMP_IDX) + SConfig::GetInstance().GetGameID(); + const auto path_drawcall = fmt::format("{}-{}", path, Common::ToUnderlying(draw_call_id)); + const auto vertex_shader_code = GenerateVertexShaderCode( + g_backend_info.api_type, m_shader_host_config, draw_data.uid->vs_uid.GetUidData()); + File::WriteStringToFile(path_drawcall + ".vs.glsl", vertex_shader_code.GetBuffer()); + const auto pixel_shader_code = GeneratePixelShaderCode( + g_backend_info.api_type, m_shader_host_config, draw_data.uid->ps_uid.GetUidData(), {}); + File::WriteStringToFile(path_drawcall + ".ps.glsl", pixel_shader_code.GetBuffer()); + const auto geometry_shader_code = GenerateGeometryShaderCode( + g_backend_info.api_type, m_shader_host_config, draw_data.uid->gs_uid.GetUidData()); + File::WriteStringToFile(path_drawcall + ".gs.glsl", geometry_shader_code.GetBuffer()); + + const auto updated_vertex_shader_code = VertexShader::WriteFullShader( + g_backend_info.api_type, m_shader_host_config, draw_data.uid->vs_uid.GetUidData(), "", ""); + File::WriteStringToFile(path_drawcall + "-updated.vs.glsl", + updated_vertex_shader_code.GetBuffer()); + const auto updated_pixel_shader_code = PixelShader::WriteFullShader( + g_backend_info.api_type, m_shader_host_config, draw_data.uid->ps_uid.GetUidData(), "", ""); + File::WriteStringToFile(path_drawcall + "-updated.ps.glsl", + updated_pixel_shader_code.GetBuffer()); + + m_state.m_editor_data.m_export_dolphin_material_draw_call = {}; + } + + if (m_state.m_editor_data.m_create_material_draw_call == draw_call_id) + { + ShaderCode ps_out; + ps_out.Write("{}\n", PixelShader::fragment_definition); + ps_out.Write("{{\n"); + + PixelShader::WriteFragmentBody(g_backend_info.api_type, m_shader_host_config, + draw_data.uid->ps_uid.GetUidData(), ps_out); + + ps_out.Write("}}\n"); + + ShaderCode vs_out; + vs_out.Write("{}\n", VertexShader::vertex_definition); + vs_out.Write("{{\n"); + + VertexShader::WriteVertexBody(g_backend_info.api_type, m_shader_host_config, + draw_data.uid->vs_uid.GetUidData(), vs_out); + + vs_out.Write("}}\n"); + + const auto material_metadata_name = + fmt::format("{}.rastermaterial", Common::ToUnderlying(draw_call_id)); + const auto shader_metadata_name = + fmt::format("{}.rastershader", Common::ToUnderlying(draw_call_id)); + const auto pixel_name = fmt::format("{}.ps.glsl", Common::ToUnderlying(draw_call_id)); + const auto vertex_name = fmt::format("{}.vs.glsl", Common::ToUnderlying(draw_call_id)); + + GraphicsModEditor::ShaderGenerationContext shader_gen_context; + shader_gen_context.metadata_name = shader_metadata_name; + + VideoCommon::RasterShaderData shader_template; + shader_template.m_pixel_source = ps_out.GetBuffer(); + shader_template.m_vertex_source = vs_out.GetBuffer(); + + shader_gen_context.shader_template = std::move(shader_template); + shader_gen_context.pixel_name = pixel_name; + shader_gen_context.vertex_name = vertex_name; + + VideoCommon::RasterMaterialData material_template; + + GraphicsModEditor::GenerateMaterial( + material_metadata_name, m_state.m_user_data.m_current_mod_path, material_template, + shader_gen_context, m_state.m_user_data.m_asset_library.get()); + + m_state.m_editor_data.m_create_material_draw_call = {}; + } + + static Common::Matrix44 identity = Common::Matrix44::Identity(); + + if (m_selected_objects.contains(draw_call_id)) + { + auto& resource_manager = Core::System::GetInstance().GetCustomResourceManager(); + auto* material_resource = resource_manager.GetMaterialFromAsset( + "editor-highlight-material", m_state.m_editor_data.m_asset_library, draw_data); + vertex_manager->DrawEmulatedMesh(material_resource, draw_data, identity, m_camera_manager); + } + else if (m_state.m_editor_data.m_view_lighting && + draw_data.projection_type != ProjectionType::Orthographic) + { + auto& resource_manager = Core::System::GetInstance().GetCustomResourceManager(); + auto* material_resource = resource_manager.GetMaterialFromAsset( + "editor-lighting-vis-material", m_state.m_editor_data.m_asset_library, draw_data); + vertex_manager->DrawEmulatedMesh(material_resource, draw_data, identity, m_camera_manager); + } + else if (m_state.m_editor_data.m_view_normals && + draw_data.projection_type != ProjectionType::Orthographic) + { + auto& resource_manager = Core::System::GetInstance().GetCustomResourceManager(); + auto* material_resource = resource_manager.GetMaterialFromAsset( + "editor-normal-vis-material", m_state.m_editor_data.m_asset_library, draw_data); + vertex_manager->DrawEmulatedMesh(material_resource, draw_data, identity, m_camera_manager); + } + else + { + if (m_state.m_editor_data.m_disable_all_actions) + { + vertex_manager->DrawEmulatedMesh(m_camera_manager); + } + else + { + if (const auto iter = m_state.m_user_data.m_draw_call_id_to_actions.find(draw_call_id); + iter != m_state.m_user_data.m_draw_call_id_to_actions.end()) + { + CustomDraw(draw_data, vertex_manager, iter->second, draw_call_id); + } + else + { + vertex_manager->DrawEmulatedMesh(m_camera_manager); + } + } + } +} + +void EditorBackend::OnTextureLoad(const GraphicsModSystem::TextureView& texture) +{ + std::string texture_cache_id{texture.hash_name}; + auto iter = m_state.m_runtime_data.m_texture_cache_id_to_data.lower_bound(texture.hash_name); + if (iter == m_state.m_runtime_data.m_texture_cache_id_to_data.end() || + iter->first != texture.hash_name) + { + m_state.m_runtime_data.m_current_xfb.m_texture_cache_ids.insert(texture_cache_id); + + GraphicsModEditor::TextureCacheData data; + data.m_id = std::move(texture_cache_id); + data.m_create_time = std::chrono::steady_clock::now(); + iter = m_state.m_runtime_data.m_texture_cache_id_to_data.emplace_hint(iter, texture.hash_name, + std::move(data)); + } + else + { + m_state.m_runtime_data.m_current_xfb.m_texture_cache_ids.insert(std::move(texture_cache_id)); + } + iter->second.texture = texture; + iter->second.m_active = true; + iter->second.m_last_load_time = std::chrono::steady_clock::now(); +} + +void EditorBackend::OnTextureUnload(GraphicsModSystem::TextureType, std::string_view texture_hash) +{ + if (auto iter = m_state.m_runtime_data.m_texture_cache_id_to_data.find(texture_hash); + iter != m_state.m_runtime_data.m_texture_cache_id_to_data.end()) + { + iter->second.m_active = false; + } +} + +void EditorBackend::OnTextureCreate(const GraphicsModSystem::TextureView& texture) +{ + GraphicsModSystem::Runtime::GraphicsModBackend::OnTextureCreate(texture); + + if (texture.texture_type == GraphicsModSystem::TextureType::XFB) + { + // Skip XFBs that have no draw calls + if (m_state.m_runtime_data.m_current_xfb.m_draw_call_ids.empty()) + return; + + // TODO: use string_view directly in C++26 (or use a different container) + const std::string xfb_hash_name{texture.hash_name}; + m_state.m_runtime_data.m_xfb_to_data[xfb_hash_name] = + std::move(m_state.m_runtime_data.m_current_xfb); + m_state.m_runtime_data.m_current_xfb = {}; + + m_state.m_scene_dumper.OnXFBCreated(xfb_hash_name); + m_xfb_counter++; + return; + } + + std::string texture_cache_id{texture.hash_name}; + GraphicsModEditor::TextureCacheData data; + data.m_id = texture_cache_id; + data.texture = texture; + data.m_create_time = std::chrono::steady_clock::now(); + m_state.m_runtime_data.m_texture_cache_id_to_data.insert_or_assign(std::move(texture_cache_id), + std::move(data)); +} + +void EditorBackend::OnLight() +{ +} + +void EditorBackend::OnFramePresented(const PresentInfo& present_info) +{ + GraphicsModSystem::Runtime::GraphicsModBackend::OnFramePresented(present_info); + + for (const auto& action : m_state.m_user_data.m_actions) + { + action->OnFrameEnd(); + } + + // Clear out the old xfbs presented + for (const auto& xfb_presented : m_state.m_runtime_data.m_xfbs_presented) + { + m_state.m_runtime_data.m_xfb_to_data.erase(xfb_presented); + } + + // Set the new ones + m_state.m_runtime_data.m_xfbs_presented.clear(); + for (const auto& xfb_presented : present_info.xfb_copy_hashes) + { + m_state.m_runtime_data.m_xfbs_presented.push_back(std::string{xfb_presented}); + } + + m_state.m_scene_dumper.OnFramePresented(m_state.m_runtime_data.m_xfbs_presented); +} + +void EditorBackend::SelectionOccurred(const std::set& selection) +{ + m_selected_objects = selection; +} + +void EditorBackend::AddIndices(OpcodeDecoder::Primitive primitive, u32 num_vertices) +{ + m_state.m_scene_dumper.AddIndices(primitive, num_vertices); +} + +void EditorBackend::ResetIndices() +{ + m_state.m_scene_dumper.ResetIndices(); +} +} // namespace GraphicsModEditor diff --git a/Source/Core/VideoCommon/GraphicsModEditor/EditorBackend.h b/Source/Core/VideoCommon/GraphicsModEditor/EditorBackend.h new file mode 100644 index 0000000000..12bc541230 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/EditorBackend.h @@ -0,0 +1,43 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "Common/HookableEvent.h" +#include "VideoCommon/GraphicsModEditor/EditorState.h" +#include "VideoCommon/GraphicsModEditor/EditorTypes.h" +#include "VideoCommon/GraphicsModSystem/Runtime/CameraManager.h" +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModBackend.h" + +namespace GraphicsModEditor +{ +class EditorBackend final : public GraphicsModSystem::Runtime::GraphicsModBackend +{ +public: + explicit EditorBackend(EditorState&); + void OnDraw(const GraphicsModSystem::DrawDataView& draw_started, + VertexManagerBase* vertex_manager) override; + void OnTextureLoad(const GraphicsModSystem::TextureView& texture) override; + void OnTextureUnload(GraphicsModSystem::TextureType texture_type, + std::string_view texture_hash) override; + void OnTextureCreate(const GraphicsModSystem::TextureView& texture) override; + void OnLight() override; + void OnFramePresented(const PresentInfo& present_info) override; + + void AddIndices(OpcodeDecoder::Primitive primitive, u32 num_vertices) override; + void ResetIndices() override; + + VideoCommon::CameraManager& GetCameraManager() { return m_camera_manager; } + +private: + void SelectionOccurred(const std::set& selection); + + EditorState& m_state; + Common::EventHook m_selection_event; + std::set m_selected_objects; + + u64 m_xfb_counter = 0; +}; +} // namespace GraphicsModEditor diff --git a/Source/Core/VideoCommon/GraphicsModEditor/EditorEvents.h b/Source/Core/VideoCommon/GraphicsModEditor/EditorEvents.h new file mode 100644 index 0000000000..e71b4335e3 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/EditorEvents.h @@ -0,0 +1,30 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "Common/HookableEvent.h" +#include "VideoCommon/Assets/CustomAssetLibrary.h" +#include "VideoCommon/GraphicsModEditor/EditorTypes.h" + +namespace GraphicsModEditor::EditorEvents +{ +// Event called when a target or action is selected +using ItemsSelectedEvent = Common::HookableEvent<"ItemsSelected", std::set>; + +// Event called when a change that can be saved occurs +using ChangeOccurredEvent = Common::HookableEvent<"ChangeOccurred">; + +// Event called when an asset should reload, the event provides the asset id +// for use in the loader +// Will also trigger the above ChangeOccurred event +using AssetReloadEvent = + Common::HookableEvent<"AssetReload", VideoCommon::CustomAssetLibrary::AssetID>; + +// Event called when requesting an asset in the asset browser +using JumpToAssetInBrowserEvent = + Common::HookableEvent<"JumpToAssetInBrowser", VideoCommon::CustomAssetLibrary::AssetID>; + +} // namespace GraphicsModEditor::EditorEvents diff --git a/Source/Core/VideoCommon/GraphicsModEditor/EditorFilter.cpp b/Source/Core/VideoCommon/GraphicsModEditor/EditorFilter.cpp new file mode 100644 index 0000000000..5576b27d27 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/EditorFilter.cpp @@ -0,0 +1,85 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModEditor/EditorFilter.h" + +#include + +#include "Common/EnumUtils.h" + +#include "VideoCommon/GraphicsModEditor/EditorState.h" +#include "VideoCommon/XFMemory.h" + +namespace GraphicsModEditor +{ + +bool DoesDrawCallMatchFilter(const DrawCallFilterContext& context, const EditorState& state, + GraphicsModSystem::DrawCallID draw_call_id) +{ + const auto runtime_iter = state.m_runtime_data.m_draw_call_id_to_data.find(draw_call_id); + if (runtime_iter == state.m_runtime_data.m_draw_call_id_to_data.end()) + return false; + + bool filter = true; + + if (!context.text.empty()) + { + if (const auto user_iter = state.m_user_data.m_draw_call_id_to_user_data.find(draw_call_id); + user_iter != state.m_user_data.m_draw_call_id_to_user_data.end() && + !user_iter->second.m_friendly_name.empty()) + { + filter &= user_iter->second.m_friendly_name.find(context.text) != std::string::npos; + } + else + { + const std::string id = fmt::to_string(Common::ToUnderlying(draw_call_id)); + filter &= id.find(context.text) != std::string::npos; + } + } + + u8 texture_count = 0; + u8 efb_count = 0; + for (const auto& texture : runtime_iter->second.draw_data.textures) + { + if (texture.texture_type == GraphicsModSystem::TextureType::Normal) + texture_count++; + + if (texture.texture_type == GraphicsModSystem::TextureType::EFB) + efb_count++; + } + + switch (context.texture_filter) + { + case DrawCallFilterContext::TextureFilter::Any: + // nop + break; + case DrawCallFilterContext::TextureFilter::TextureOnly: + filter &= texture_count > 0 && efb_count == 0; + break; + case DrawCallFilterContext::TextureFilter::EFBOnly: + filter &= texture_count == 0 && efb_count > 0; + break; + case DrawCallFilterContext::TextureFilter::Both: + filter &= texture_count > 0 && efb_count > 0; + break; + case DrawCallFilterContext::TextureFilter::None: + filter &= texture_count == 0 && efb_count == 0; + break; + }; + + switch (context.projection_filter) + { + case DrawCallFilterContext::ProjectionFilter::Any: + // nop + break; + case DrawCallFilterContext::ProjectionFilter::Orthographic: + filter &= runtime_iter->second.draw_data.projection_type == ProjectionType::Orthographic; + break; + case DrawCallFilterContext::ProjectionFilter::Perspective: + filter &= runtime_iter->second.draw_data.projection_type == ProjectionType::Perspective; + break; + }; + + return filter; +} +} // namespace GraphicsModEditor diff --git a/Source/Core/VideoCommon/GraphicsModEditor/EditorFilter.h b/Source/Core/VideoCommon/GraphicsModEditor/EditorFilter.h new file mode 100644 index 0000000000..f21936e732 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/EditorFilter.h @@ -0,0 +1,37 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "VideoCommon/GraphicsModSystem/Types.h" + +namespace GraphicsModEditor +{ +struct EditorState; + +struct DrawCallFilterContext +{ + std::string text; + + enum class TextureFilter + { + Any, + EFBOnly, + TextureOnly, + Both, + None + }; + TextureFilter texture_filter = TextureFilter::Any; + + enum class ProjectionFilter + { + Any, + Orthographic, + Perspective + }; + ProjectionFilter projection_filter = ProjectionFilter::Any; +}; + +bool DoesDrawCallMatchFilter(const DrawCallFilterContext& context, const EditorState& state, + GraphicsModSystem::DrawCallID draw_call_id); +} // namespace GraphicsModEditor diff --git a/Source/Core/VideoCommon/GraphicsModEditor/EditorMain.cpp b/Source/Core/VideoCommon/GraphicsModEditor/EditorMain.cpp new file mode 100644 index 0000000000..00b3eaf387 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/EditorMain.cpp @@ -0,0 +1,667 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModEditor/EditorMain.h" + +#include +#include + +#include +#include + +#include "Common/CommonPaths.h" +#include "Common/EnumUtils.h" +#include "Common/FileUtil.h" +#include "Common/JsonUtil.h" +#include "Core/ConfigManager.h" +#include "Core/System.h" + +#include "VideoCommon/AbstractGfx.h" +#include "VideoCommon/AbstractTexture.h" +#include "VideoCommon/Assets/CustomAssetLoader.h" +#include "VideoCommon/Assets/CustomTextureData.h" +#include "VideoCommon/GraphicsModEditor/Controls/MeshExtractWindow.h" +#include "VideoCommon/GraphicsModEditor/EditorBackend.h" +#include "VideoCommon/GraphicsModEditor/EditorEvents.h" +#include "VideoCommon/GraphicsModEditor/EditorState.h" +#include "VideoCommon/GraphicsModEditor/EditorTypes.h" +#include "VideoCommon/GraphicsModEditor/Panels/ActiveTargetsPanel.h" +#include "VideoCommon/GraphicsModEditor/Panels/AssetBrowserPanel.h" +#include "VideoCommon/GraphicsModEditor/Panels/PropertiesPanel.h" +#include "VideoCommon/GraphicsModSystem/Config/GraphicsMod.h" +#include "VideoCommon/GraphicsModSystem/Runtime/Actions/ModifyLight.h" +#include "VideoCommon/GraphicsModSystem/Runtime/CustomResourceManager.h" +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h" +#include "VideoCommon/HiresTextures.h" +#include "VideoCommon/TextureCacheBase.h" +#include "VideoCommon/VideoConfig.h" + +namespace GraphicsModEditor +{ +namespace +{ +bool AddTextureToResources(const std::string& texture_path, const std::string& name, + EditorState* state) +{ + VideoCommon::CustomTextureData::ArraySlice::Level level; + if (!VideoCommon::LoadPNGTexture(&level, texture_path)) + { + return false; + } + + TextureConfig tex_config(level.width, level.height, 1, 1, 1, AbstractTextureFormat::RGBA8, 0, + AbstractTextureType::Texture_2DArray); + auto editor_tex = g_gfx->CreateTexture(tex_config, name); + if (!editor_tex) + { + PanicAlertFmt("Failed to create editor texture '{}'", name); + return false; + } + + editor_tex->Load(0, level.width, level.height, level.width, level.data.data(), + sizeof(u32) * level.width * level.height); + state->m_editor_data.m_name_to_texture[name] = std::move(editor_tex); + + return true; +} + +bool AddTemplate(const std::string& template_path, const std::string& name, EditorState* state) +{ + std::string template_data; + if (!File::ReadFileToString(template_path, template_data)) + { + PanicAlertFmt("Failed to load editor template '{}'", name); + return false; + } + + state->m_editor_data.m_name_to_template[name] = template_data; + return true; +} +} // namespace +EditorMain::EditorMain() = default; +EditorMain::~EditorMain() = default; + +bool EditorMain::Initialize() +{ + if (!RebuildState()) + return false; + + ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; + + // TODO + m_enabled = true; + + return true; +} + +void EditorMain::Shutdown() +{ + m_active_targets_panel.reset(); + m_asset_browser_panel.reset(); + m_properties_panel.reset(); + + auto& system = Core::System::GetInstance(); + system.GetGraphicsModManager().SetEditorBackend(nullptr); + + m_state.reset(); + + m_has_changes = false; + m_enabled = false; + m_editor_session_in_progress = false; +} + +void EditorMain::DrawImGui() +{ + if (!m_enabled) + return; + + DrawMenu(); + + if (m_editor_session_in_progress) + { + m_active_targets_panel->DrawImGui(); + + if (!m_inspect_only) + m_asset_browser_panel->DrawImGui(); + m_properties_panel->DrawImGui(); + } + else + { + // ImGui::ShowDemoWindow(); + } +} + +bool EditorMain::RebuildState() +{ + m_state = std::make_unique(); + m_active_targets_panel = std::make_unique(*m_state); + m_asset_browser_panel = std::make_unique(*m_state); + m_properties_panel = std::make_unique(*m_state); + + m_state->m_user_data.m_asset_library = std::make_shared(); + m_state->m_editor_data.m_asset_library = + std::make_shared(); + m_state->m_editor_data.m_asset_library->Watch(File::GetSysDirectory() + GRAPHICSMODEDITOR_DIR); + m_change_occurred_event = + EditorEvents::ChangeOccurredEvent::Register([this] { OnChangeOccured(); }, "EditorMain"); + const std::string textures_path_root = + File::GetSysDirectory() + GRAPHICSMODEDITOR_DIR + "/Textures"; + if (!AddTextureToResources(fmt::format("{}/icons8-portraits-50.png", textures_path_root), + "hollow_cube", m_state.get())) + { + return false; + } + if (!AddTextureToResources(fmt::format("{}/icons8-document-500.png", textures_path_root), "file", + m_state.get())) + { + return false; + } + if (!AddTextureToResources(fmt::format("{}/icons8-folder-50.png", textures_path_root), + "tiny_folder", m_state.get())) + { + return false; + } + if (!AddTextureToResources(fmt::format("{}/icons8-folder-500.png", textures_path_root), "folder", + m_state.get())) + { + return false; + } + if (!AddTextureToResources(fmt::format("{}/icons8-image-file-500.png", textures_path_root), + "image", m_state.get())) + { + return false; + } + if (!AddTextureToResources(fmt::format("{}/icons8-code-file-100.png", textures_path_root), "code", + m_state.get())) + { + return false; + } + if (!AddTextureToResources(fmt::format("{}/icons8-cube-filled-50.png", textures_path_root), + "filled_cube", m_state.get())) + { + return false; + } + if (!AddTextureToResources(fmt::format("{}/icons8-search-50.png", textures_path_root), "search", + m_state.get())) + { + return false; + } + if (!AddTextureToResources(fmt::format("{}/icons8-error-50.png", textures_path_root), "error", + m_state.get())) + { + return false; + } + + const std::string templates_path_root = + File::GetSysDirectory() + GRAPHICSMODEDITOR_DIR + "/Templates"; + if (!AddTemplate(fmt::format("{}/raster_material.json", templates_path_root), "raster_material", + m_state.get())) + { + return false; + } + if (!AddTemplate(fmt::format("{}/raster_shader.json", templates_path_root), "raster_shader_meta", + m_state.get())) + { + return false; + } + if (!AddTemplate(fmt::format("{}/raster_shader.ps.glsl", templates_path_root), "raster_shader_ps", + m_state.get())) + { + return false; + } + if (!AddTemplate(fmt::format("{}/raster_shader.vs.glsl", templates_path_root), "raster_shader_vs", + m_state.get())) + { + return false; + } + + auto& system = Core::System::GetInstance(); + auto& asset_loader = system.GetCustomAssetLoader(); + auto& custom_resource_manager = system.GetCustomResourceManager(); + asset_loader.Reset(); + custom_resource_manager.Reset(); + + m_asset_reload_event = EditorEvents::AssetReloadEvent::Register( + [&custom_resource_manager, this](const VideoCommon::CustomAssetLibrary::AssetID& asset_id) { + if (asset_id != "") + { + custom_resource_manager.ReloadAsset(asset_id); + OnChangeOccured(); + } + }, + "EditorMain"); + + const std::string pipeline_path_root = + File::GetSysDirectory() + GRAPHICSMODEDITOR_DIR + "/Pipelines"; + const std::filesystem::path pipeline_path_root_fs{pipeline_path_root}; + + { + VideoCommon::Assets::AssetMap highlight_shader_asset_map; + highlight_shader_asset_map.try_emplace("metadata", pipeline_path_root_fs / "highlight" / + "color.rastershader"); + highlight_shader_asset_map.try_emplace("pixel_shader", + pipeline_path_root_fs / "highlight" / "color.ps.glsl"); + highlight_shader_asset_map.try_emplace("vertex_shader", + pipeline_path_root_fs / "highlight" / "color.vs.glsl"); + m_state->m_editor_data.m_asset_library->SetAssetIDMapData( + "highlight_shader", std::move(highlight_shader_asset_map)); + + VideoCommon::Assets::AssetMap highlight_material_asset_map; + highlight_material_asset_map.try_emplace("", pipeline_path_root_fs / "highlight" / + "highlight.rastermaterial"); + m_state->m_editor_data.m_asset_library->SetAssetIDMapData( + "editor-highlight-material", std::move(highlight_material_asset_map)); + } + + { + VideoCommon::Assets::AssetMap simple_light_visual_shader_asset_map; + simple_light_visual_shader_asset_map.try_emplace("metadata", + pipeline_path_root_fs / "light_visualization" / + "simple-light-visualization.rastershader"); + simple_light_visual_shader_asset_map.try_emplace("pixel_shader", + pipeline_path_root_fs / "light_visualization" / + "simple-light-visualization.ps.glsl"); + simple_light_visual_shader_asset_map.try_emplace("vertex_shader", + pipeline_path_root_fs / "light_visualization" / + "simple-light-visualization.vs.glsl"); + m_state->m_editor_data.m_asset_library->SetAssetIDMapData( + "simple_light_visualization_shader", std::move(simple_light_visual_shader_asset_map)); + + VideoCommon::Assets::AssetMap simple_light_visual_material_asset_map; + simple_light_visual_material_asset_map.try_emplace( + "", pipeline_path_root_fs / "light_visualization" / + "simple-light-visualization.rastermaterial"); + m_state->m_editor_data.m_asset_library->SetAssetIDMapData( + "editor-lighting-vis-material", std::move(simple_light_visual_material_asset_map)); + } + + { + VideoCommon::Assets::AssetMap normal_visual_shader_asset_map; + normal_visual_shader_asset_map.try_emplace("metadata", pipeline_path_root_fs / + "normal_visualization" / + "normal-visualization.rastershader"); + normal_visual_shader_asset_map.try_emplace("pixel_shader", pipeline_path_root_fs / + "normal_visualization" / + "normal-visualization.ps.glsl"); + normal_visual_shader_asset_map.try_emplace("vertex_shader", pipeline_path_root_fs / + "normal_visualization" / + "normal-visualization.vs.glsl"); + m_state->m_editor_data.m_asset_library->SetAssetIDMapData( + "normal_visualization_shader", std::move(normal_visual_shader_asset_map)); + + VideoCommon::Assets::AssetMap normal_visual_material_asset_map; + normal_visual_material_asset_map.try_emplace( + "", pipeline_path_root_fs / "normal_visualization" / "normal-visualization.rastermaterial"); + m_state->m_editor_data.m_asset_library->SetAssetIDMapData( + "editor-normal-vis-material", std::move(normal_visual_material_asset_map)); + } + + { + GraphicsModSystem::Config::GraphicsModTag tag; + tag.m_name = "Bloom"; + tag.m_description = "Post processing effect that blurs out the brightest areas of the screen"; + tag.m_color = Common::Vec3{0.66f, 0.63f, 0.16f}; + m_state->m_editor_data.m_tags[tag.m_name] = tag; + } + + { + GraphicsModSystem::Config::GraphicsModTag tag; + tag.m_name = "Depth of Field"; + tag.m_description = "Post processing effect that blurs distant objects"; + tag.m_color = Common::Vec3{0.8f, 0, 0}; + m_state->m_editor_data.m_tags[tag.m_name] = tag; + } + + { + GraphicsModSystem::Config::GraphicsModTag tag; + tag.m_name = "User Interface"; + tag.m_description = ""; + tag.m_color = Common::Vec3{0.01f, 0.04f, 0.99f}; + m_state->m_editor_data.m_tags[tag.m_name] = tag; + } + + return true; +} + +void EditorMain::DrawMenu() +{ + bool new_mod_popup = false; + if (ImGui::BeginMainMenuBar()) + { + if (ImGui::BeginMenu("File")) + { + if (ImGui::BeginMenu("New")) + { + if (ImGui::MenuItem("Project")) + { + new_mod_popup = true; + } + if (ImGui::MenuItem("Inspect Only")) + { + m_editor_session_in_progress = true; + m_inspect_only = true; + + RebuildState(); + auto& system = Core::System::GetInstance(); + system.GetGraphicsModManager().SetEditorBackend( + std::make_unique(*m_state)); + } + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Open")) + { + const std::string& game_id = SConfig::GetInstance().GetGameID(); + const auto directories = + GetTextureDirectoriesWithGameId(File::GetUserPath(D_GRAPHICSMOD_IDX), game_id); + if (directories.empty()) + { + ImGui::Text("No available projects, create a new project instead"); + } + else + { + for (const auto& directory : directories) + { + const auto name = StringToPath(directory).filename(); + const auto name_str = PathToString(name); + if (ImGui::MenuItem(name_str.c_str())) + { + LoadMod(name_str); + } + } + } + ImGui::EndMenu(); + } + if (ImGui::MenuItem("Save", "Ctrl+S", false, + m_has_changes && m_editor_session_in_progress && !m_inspect_only)) + { + Save(); + } + if (ImGui::MenuItem("Save As..", nullptr, false, false)) + { + // TODO + } + if (ImGui::MenuItem("Close", nullptr, false, m_editor_session_in_progress)) + { + Close(); + } + + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Scene")) + { + if (ImGui::MenuItem("Export Scene As Mesh", nullptr, false, m_editor_session_in_progress)) + { + m_open_mesh_dump_export_window = true; + } + if (ImGui::MenuItem("Export Scene Metadata As JSON", nullptr, false, + m_editor_session_in_progress)) + { + picojson::array data_objects; + for (const auto& [draw_call_id, data] : m_state->m_runtime_data.m_draw_call_id_to_data) + { + picojson::object obj; + obj.emplace("draw_call_id", fmt::to_string(Common::ToUnderlying(draw_call_id))); + obj.emplace("projection type", fmt::to_string(data.draw_data.projection_type)); + // TODO: blending, depth, rasterization... + + picojson::array textures; + for (const auto& texture : data.draw_data.textures) + { + picojson::object texture_obj; + texture_obj.emplace("hash", texture.hash_name); + if (texture.texture_type == GraphicsModSystem::TextureType::Normal) + { + texture_obj.emplace("type", "Normal"); + } + else if (texture.texture_type == GraphicsModSystem::TextureType::EFB) + { + texture_obj.emplace("type", "EFB"); + } + else if (texture.texture_type == GraphicsModSystem::TextureType::XFB) + { + texture_obj.emplace("type", "XFB"); + } + + textures.push_back(picojson::value{texture_obj}); + } + obj.emplace("textures", picojson::value{textures}); + data_objects.push_back(picojson::value{obj}); + } + const std::string path = File::GetUserPath(D_DUMP_IDX) + SConfig::GetInstance().GetGameID(); + JsonToFile(PathToString(path + "_SceneMetadata.json"), picojson::value{data_objects}, true); + } + if (ImGui::MenuItem("Disable all actions", nullptr, + &m_state->m_editor_data.m_disable_all_actions)) + { + } + if (ImGui::MenuItem("View lighting", nullptr, &m_state->m_editor_data.m_view_lighting)) + { + } + if (ImGui::MenuItem("View normals", nullptr, &m_state->m_editor_data.m_view_normals)) + { + } + ImGui::EndMenu(); + } + + ImGui::EndMainMenuBar(); + } + + if (m_open_mesh_dump_export_window && m_state) + { + if (Controls::ShowMeshExtractWindow(m_state->m_scene_dumper, m_last_mesh_dump_request)) + { + m_open_mesh_dump_export_window = false; + m_last_mesh_dump_request = {}; + } + } + + const std::string_view new_graphics_mod_popup_name = "New Graphics Mod"; + if (new_mod_popup) + { + if (!ImGui::IsPopupOpen(new_graphics_mod_popup_name.data())) + { + m_editor_new_mod_name = ""; + m_editor_new_mod_author = ""; + m_editor_new_mod_description = ""; + ImGui::OpenPopup(new_graphics_mod_popup_name.data()); + } + } + + // New mod popup below + const ImVec2 center = ImGui::GetMainViewport()->GetCenter(); + ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); + if (ImGui::BeginPopupModal(new_graphics_mod_popup_name.data(), nullptr)) + { + bool is_valid = false; + + if (ImGui::BeginTable("NewModForm", 2)) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Name"); + ImGui::TableNextColumn(); + ImGui::InputText("##NewModName", &m_editor_new_mod_name); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Author"); + ImGui::TableNextColumn(); + ImGui::InputText("##NewModAuthor", &m_editor_new_mod_author); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Description"); + ImGui::TableNextColumn(); + ImGui::InputTextMultiline("##NewModDescription", &m_editor_new_mod_description); + ImGui::EndTable(); + } + + const std::string& graphics_mod_root = File::GetUserPath(D_GRAPHICSMOD_IDX); + if (!std::filesystem::exists(std::filesystem::path{graphics_mod_root} / m_editor_new_mod_name)) + { + is_valid = true; + } + + if (!is_valid) + ImGui::BeginDisabled(); + if (ImGui::Button("Create", ImVec2(120, 0))) + { + NewMod(m_editor_new_mod_name, m_editor_new_mod_author, m_editor_new_mod_description); + ImGui::CloseCurrentPopup(); + } + if (!is_valid) + ImGui::EndDisabled(); + ImGui::SameLine(); + if (ImGui::Button("Cancel", ImVec2(120, 0))) + { + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } +} + +bool EditorMain::NewMod(std::string_view name, std::string_view author, + std::string_view description) +{ + const std::string& graphics_mod_root = File::GetUserPath(D_GRAPHICSMOD_IDX); + const std::filesystem::path mod_path = std::filesystem::path{graphics_mod_root} / name; + if (std::filesystem::exists(mod_path)) + { + // TODO: error + return false; + } + + std::error_code error_code; + std::filesystem::create_directory(mod_path, error_code); + if (error_code) + { + // TODO: error + return false; + } + + const std::string& game_id = SConfig::GetInstance().GetGameID(); + if (!File::WriteStringToFile(PathToString(mod_path / fmt::format("{}.txt", game_id)), "")) + return false; + + if (!RebuildState()) + return false; + + m_state->m_user_data.m_title = name; + m_state->m_user_data.m_author = author; + m_state->m_user_data.m_description = description; + m_state->m_user_data.m_current_mod_path = mod_path; + m_has_changes = false; + m_editor_session_in_progress = true; + m_inspect_only = false; + + m_asset_browser_panel->ResetCurrentPath(); + m_state->m_user_data.m_asset_library->Watch(PathToString(mod_path)); + + auto& system = Core::System::GetInstance(); + system.GetGraphicsModManager().SetEditorBackend(std::make_unique(*m_state)); + + return true; +} + +bool EditorMain::LoadMod(std::string_view name) +{ + const std::string& graphics_mod_root = File::GetUserPath(D_GRAPHICSMOD_IDX); + const std::filesystem::path mod_path = std::filesystem::path{graphics_mod_root} / name; + if (!std::filesystem::exists(mod_path)) + { + // TODO: error + return false; + } + + if (!RebuildState()) + return false; + + const auto config = + GraphicsModSystem::Config::GraphicsMod::Create(PathToString(mod_path / "metadata.json")); + if (!config) + { + // TODO: error + return false; + } + + ReadFromGraphicsMod(&m_state->m_user_data, &m_state->m_editor_data, m_state->m_runtime_data, + *config, PathToString(mod_path)); + + auto& system = Core::System::GetInstance(); + auto& loader = system.GetCustomAssetLoader(); + for (const auto& asset : config->m_assets) + { + // TODO: generate preview for other types? + if (asset.m_map.find("texture") != asset.m_map.end()) + { + m_state->m_editor_data.m_assets_waiting_for_preview.try_emplace( + asset.m_asset_id, + loader.LoadGameTexture(asset.m_asset_id, m_state->m_user_data.m_asset_library)); + } + } + + m_has_changes = false; + m_editor_session_in_progress = true; + m_inspect_only = false; + + m_asset_browser_panel->ResetCurrentPath(); + m_state->m_user_data.m_asset_library->Watch(PathToString(mod_path)); + + system.GetGraphicsModManager().SetEditorBackend(std::make_unique(*m_state)); + + return true; +} + +void EditorMain::Save() +{ + if (!m_has_changes) + return; + + const std::string file_path = + PathToString(m_state->m_user_data.m_current_mod_path / "metadata.json"); + std::ofstream json_stream; + File::OpenFStream(json_stream, file_path, std::ios_base::out); + if (!json_stream.is_open()) + { + ERROR_LOG_FMT(VIDEO, "Failed to open graphics mod json file '{}' for writing", file_path); + return; + } + + m_state->m_user_data.m_asset_library->SaveAssetDataAsFiles(); + + GraphicsModSystem::Config::GraphicsMod mod; + WriteToGraphicsMod(m_state->m_user_data, &mod); + + picojson::object serialized_root; + mod.Serialize(serialized_root); + + const auto output = picojson::value{serialized_root}.serialize(true); + json_stream << output; + + m_has_changes = false; +} + +void EditorMain::Close() +{ + if (m_has_changes) + { + // Ask "are you sure?" + } + + m_editor_session_in_progress = false; + m_inspect_only = false; + + auto& system = Core::System::GetInstance(); + system.GetGraphicsModManager().SetEditorBackend(nullptr); +} + +void EditorMain::OnChangeOccured() +{ + m_has_changes = true; +} + +EditorState* EditorMain::GetEditorState() const +{ + return m_state.get(); +} +} // namespace GraphicsModEditor diff --git a/Source/Core/VideoCommon/GraphicsModEditor/EditorMain.h b/Source/Core/VideoCommon/GraphicsModEditor/EditorMain.h new file mode 100644 index 0000000000..cd352dbb3b --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/EditorMain.h @@ -0,0 +1,89 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#include "Common/HookableEvent.h" +#include "VideoCommon/GraphicsModEditor/SceneDumper.h" +#include "VideoCommon/GraphicsModSystem/Types.h" +#include "VideoCommon/XFMemory.h" + +struct FBInfo; +class GraphicsModAction; + +namespace GraphicsModEditor +{ +struct DrawCallData; +struct FBCallData; +struct LightData; +struct EditorState; + +namespace Panels +{ +class ActiveTargetsPanel; +class AssetBrowserPanel; +class PropertiesPanel; +} // namespace Panels +class EditorMain +{ +public: + EditorMain(); + ~EditorMain(); + + bool Initialize(); + void Shutdown(); + + bool IsEnabled() const { return m_enabled; } + + // Creates a new graphics mod for editing + bool NewMod(std::string_view name, std::string_view author, std::string_view description); + + // Loads an existing graphics mod for editing + bool LoadMod(std::string_view name); + + // Renders ImGui windows to the currently-bound framebuffer. + // Should be called by main UI manager + void DrawImGui(); + + EditorState* GetEditorState() const; + +private: + bool RebuildState(); + + void DrawMenu(); + + // Saves the current editor session + void Save(); + + // Closes this editor session + void Close(); + + void OnChangeOccured(); + Common::EventHook m_change_occurred_event; + Common::EventHook m_asset_reload_event; + bool m_has_changes = false; + bool m_editor_session_in_progress = false; + bool m_enabled = false; + + // Inspect mode allows the user to look and add some basic + // graphics mods but they can't create any new files or save + bool m_inspect_only = false; + + std::unique_ptr m_state; + + std::unique_ptr m_active_targets_panel; + std::unique_ptr m_asset_browser_panel; + std::unique_ptr m_properties_panel; + + std::string m_editor_new_mod_name; + std::string m_editor_new_mod_author; + std::string m_editor_new_mod_description; + + SceneDumper::RecordingRequest m_last_mesh_dump_request; + bool m_open_mesh_dump_export_window = false; +}; +} // namespace GraphicsModEditor diff --git a/Source/Core/VideoCommon/GraphicsModEditor/EditorState.cpp b/Source/Core/VideoCommon/GraphicsModEditor/EditorState.cpp new file mode 100644 index 0000000000..25bb1c945e --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/EditorState.cpp @@ -0,0 +1,245 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModEditor/EditorState.h" + +#include + +#include + +#include "Common/EnumUtils.h" +#include "Common/Logging/Log.h" +#include "Common/StringUtil.h" +#include "Common/VariantUtil.h" + +#include "VideoCommon/GraphicsModSystem/Config/GraphicsMod.h" +#include "VideoCommon/GraphicsModSystem/Constants.h" +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.h" + +namespace GraphicsModEditor +{ +void WriteToGraphicsMod(const UserData& user_data, GraphicsModSystem::Config::GraphicsMod* config) +{ + config->m_title = user_data.m_title; + config->m_author = user_data.m_author; + config->m_description = user_data.m_description; + + config->m_assets = user_data.m_asset_library->GetAssets(user_data.m_current_mod_path); + config->m_tags = user_data.m_tags; + + std::map action_to_index; + for (const auto& action : user_data.m_actions) + { + GraphicsModSystem::Config::GraphicsModAction action_config; + action_config.m_factory_name = action->GetFactoryName(); + + picojson::object serialized_data; + action->SerializeToConfig(&serialized_data); + action_config.m_data = picojson::value{serialized_data}; + + config->m_actions.push_back(std::move(action_config)); + action_to_index[action.get()] = config->m_actions.size() - 1; + } + + for (const auto& [tag_name, actions] : user_data.m_tag_name_to_actions) + { + auto& action_indexes = config->m_tag_name_to_action_indexes[tag_name]; + for (const auto& action : actions) + { + action_indexes.push_back(action_to_index[action]); + } + } + + std::set draw_calls; + for (const auto& [draw_call_id, actions] : user_data.m_draw_call_id_to_actions) + { + draw_calls.insert(draw_call_id); + } + for (const auto& [draw_call_id, actions] : user_data.m_draw_call_id_to_user_data) + { + draw_calls.insert(draw_call_id); + } + + for (const auto& draw_call_id : draw_calls) + { + const auto target_index = config->m_targets.size(); + GraphicsModSystem::Config::IntTarget i_target; + i_target.m_target_id = Common::ToUnderlying(draw_call_id); + + if (const auto iter = user_data.m_draw_call_id_to_actions.find(draw_call_id); + iter != user_data.m_draw_call_id_to_actions.end()) + { + auto& action_indexes = config->m_target_index_to_action_indexes[target_index]; + for (const auto& action : iter->second) + { + action_indexes.push_back(action_to_index[action]); + } + } + + if (const auto iter = user_data.m_draw_call_id_to_user_data.find(draw_call_id); + iter != user_data.m_draw_call_id_to_user_data.end()) + { + i_target.m_name = iter->second.m_friendly_name; + + for (const auto& tag_name : iter->second.m_tag_names) + { + i_target.m_tag_names.push_back(tag_name); + } + } + config->m_targets.push_back(std::move(i_target)); + } + + std::set texture_cache_ids; + for (const auto& [texture_cache_id, actions] : user_data.m_texture_cache_id_to_actions) + { + texture_cache_ids.insert(texture_cache_id); + } + for (const auto& [texture_cache_id, actions] : user_data.m_texture_cache_id_to_user_data) + { + texture_cache_ids.insert(texture_cache_id); + } + + for (const auto& texture_cache_id : texture_cache_ids) + { + const auto target_index = config->m_targets.size(); + GraphicsModSystem::Config::StringTarget s_target; + s_target.m_target_id = texture_cache_id; + + if (const auto iter = user_data.m_texture_cache_id_to_actions.find(texture_cache_id); + iter != user_data.m_texture_cache_id_to_actions.end()) + { + auto& action_indexes = config->m_target_index_to_action_indexes[target_index]; + for (const auto& action : iter->second) + { + action_indexes.push_back(action_to_index[action]); + } + } + + if (const auto iter = user_data.m_texture_cache_id_to_user_data.find(texture_cache_id); + iter != user_data.m_texture_cache_id_to_user_data.end()) + { + s_target.m_name = iter->second.m_friendly_name; + + for (const auto& tag_name : iter->second.m_tag_names) + { + s_target.m_tag_names.push_back(tag_name); + } + } + config->m_targets.push_back(std::move(s_target)); + } +} + +void ReadFromGraphicsMod(UserData* user_data, EditorData* editor_data, + const RuntimeState& runtime_state, + const GraphicsModSystem::Config::GraphicsMod& config, + const std::string& mod_root) +{ + user_data->m_title = config.m_title; + user_data->m_author = config.m_author; + user_data->m_description = config.m_description; + user_data->m_current_mod_path = mod_root; + + user_data->m_asset_library->AddAssets(config.m_assets, user_data->m_current_mod_path); + + const auto create_action = + [&](const std::string_view& action_name, + const picojson::value& json_data) -> std::unique_ptr { + auto action = + GraphicsModActionFactory::Create(action_name, json_data, user_data->m_asset_library); + if (action == nullptr) + { + return nullptr; + } + return action; + }; + + for (const auto& action_config : config.m_actions) + { + if (auto action = create_action(action_config.m_factory_name, action_config.m_data)) + { + user_data->m_actions.push_back(std::make_unique(std::move(action))); + user_data->m_actions.back().get()->SetID(editor_data->m_next_action_id); + editor_data->m_next_action_id++; + } + } + + user_data->m_tags = config.m_tags; + + for (const auto& target : config.m_targets) + { + std::visit( + overloaded{ + [&](const GraphicsModSystem::Config::IntTarget& int_target) { + const GraphicsModSystem::DrawCallID draw_call_id{int_target.m_target_id}; + auto& actions = user_data->m_draw_call_id_to_actions[draw_call_id]; + actions = {}; + + if (int_target.m_name != "") + { + user_data->m_draw_call_id_to_user_data[draw_call_id].m_friendly_name = + int_target.m_name; + } + + for (const auto& tag_name : int_target.m_tag_names) + { + user_data->m_draw_call_id_to_user_data[draw_call_id].m_tag_names.push_back( + tag_name); + } + }, + [&](const GraphicsModSystem::Config::StringTarget& str_target) { + const GraphicsModSystem::TextureCacheID texture_cache_id{str_target.m_target_id}; + auto& actions = user_data->m_texture_cache_id_to_actions[texture_cache_id]; + actions = {}; + + if (str_target.m_name != "") + { + user_data->m_texture_cache_id_to_user_data[texture_cache_id].m_friendly_name = + str_target.m_name; + } + + for (const auto& tag_name : str_target.m_tag_names) + { + user_data->m_texture_cache_id_to_user_data[texture_cache_id].m_tag_names.push_back( + tag_name); + } + }}, + target); + } + + for (const auto& target_to_actions_pair : config.m_target_index_to_action_indexes) + { + const u64 target_index = target_to_actions_pair.first; + const std::vector& action_indexes = target_to_actions_pair.second; + std::visit( + overloaded{[&](const GraphicsModSystem::Config::IntTarget& int_target) { + const GraphicsModSystem::DrawCallID draw_call_id{int_target.m_target_id}; + auto& actions = user_data->m_draw_call_id_to_actions[draw_call_id]; + + for (const auto& action_index : action_indexes) + { + actions.push_back(user_data->m_actions[action_index].get()); + } + }, + [&](const GraphicsModSystem::Config::StringTarget& str_target) { + const GraphicsModSystem::TextureCacheID texture_cache_id{ + str_target.m_target_id}; + auto& actions = user_data->m_texture_cache_id_to_actions[texture_cache_id]; + + for (const auto& action_index : action_indexes) + { + actions.push_back(user_data->m_actions[action_index].get()); + } + }}, + config.m_targets[target_index]); + } + + for (const auto& [tag_name, action_indexes] : config.m_tag_name_to_action_indexes) + { + auto& actions = user_data->m_tag_name_to_actions[tag_name]; + for (const auto& action_index : action_indexes) + { + actions.push_back(user_data->m_actions[action_index].get()); + } + } +} +} // namespace GraphicsModEditor diff --git a/Source/Core/VideoCommon/GraphicsModEditor/EditorState.h b/Source/Core/VideoCommon/GraphicsModEditor/EditorState.h new file mode 100644 index 0000000000..705174e994 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/EditorState.h @@ -0,0 +1,166 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "Common/CommonTypes.h" + +#include "VideoCommon/AbstractTexture.h" +#include "VideoCommon/Assets/CustomAsset.h" +#include "VideoCommon/Assets/DirectFilesystemAssetLibrary.h" +#include "VideoCommon/GraphicsModEditor/EditorAssetSource.h" +#include "VideoCommon/GraphicsModEditor/EditorTypes.h" +#include "VideoCommon/GraphicsModEditor/SceneDumper.h" +#include "VideoCommon/GraphicsModSystem/Config/GraphicsModHashPolicy.h" +#include "VideoCommon/GraphicsModSystem/Config/GraphicsModTag.h" +#include "VideoCommon/GraphicsModSystem/Runtime/CustomTextureCache.h" +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h" +#include "VideoCommon/GraphicsModSystem/Types.h" + +namespace GraphicsModSystem::Config +{ +struct GraphicsMod; +} + +namespace GraphicsModEditor +{ +struct EditorData +{ + // A map of editor specific textures accessible by name + std::map> m_name_to_texture; + + // A map of editor specific templates accessible by name + std::map m_name_to_template; + + // Editor specific tags + std::map m_tags; + + // User data that is waiting on the editor to generate + // a preview + std::map> + m_assets_waiting_for_preview; + + std::shared_ptr m_asset_library; + + bool m_disable_all_actions = false; + + bool m_view_lighting = false; + bool m_view_normals = false; + + GraphicsModSystem::DrawCallID m_export_dolphin_material_draw_call; + + GraphicsModSystem::DrawCallID m_create_material_draw_call; + + u64 m_next_action_id = 1; +}; + +struct UserData +{ + std::string m_title; + std::string m_author; + std::string m_description; + std::filesystem::path m_current_mod_path; + + GraphicsModSystem::Config::HashPolicy m_hash_policy = + GraphicsModSystem::Config::GetDefaultHashPolicy(); + + // References of actions defined by the user to provide to the graphics mod interface + std::map> + m_draw_call_id_to_actions; + + // References of actions defined by the user to provide to the graphics mod interface + std::map> + m_texture_cache_id_to_actions; + + // References of actions defined by the user to provide to the graphics mod interface + std::map> + m_light_id_to_reference_actions; + + std::vector> m_actions; + + std::vector m_tags; + + // References of actions defined by the user to provide to the graphics mod interface + std::map> m_tag_name_to_actions; + + // Mapping the draw call id to any user data for the target + std::map m_draw_call_id_to_user_data; + + // Mapping the texture cache id to any user data for the target + std::map m_texture_cache_id_to_user_data; + + // Mapping the light id to any user data for the target + std::map m_light_id_to_user_data; + + // The asset library provided to all user actions + std::shared_ptr m_asset_library; +}; + +struct RuntimeState +{ + struct XFBData + { + struct DrawCallWithTime + { + GraphicsModSystem::DrawCallID draw_call_id; + std::chrono::steady_clock::time_point sort_time; + }; + + struct DrawCallWithTimeCompare + { + bool operator()(const DrawCallWithTime& lhs, const DrawCallWithTime& rhs) const + { + return lhs.sort_time < rhs.sort_time; + } + }; + + // The draw call ids this frame + std::set m_draw_call_ids; + + // The EFB/XFB calls triggered this frame + std::set m_texture_cache_ids; + + // The game lights triggered this frame + std::set m_light_ids; + }; + std::map> m_xfb_to_data; + std::vector m_xfbs_presented; + + XFBData m_current_xfb; + + // Mapping the draw call id to the last known data + std::map m_draw_call_id_to_data; + + // Mapping the EFB/XFB calls to the last known data + std::map> + m_texture_cache_id_to_data; + + // Mapping the game light id to the last known data + std::map m_light_id_to_data; +}; + +struct EditorState +{ + EditorData m_editor_data; + UserData m_user_data; + RuntimeState m_runtime_data; + SceneDumper m_scene_dumper; +}; + +void WriteToGraphicsMod(const UserData& user_data, GraphicsModSystem::Config::GraphicsMod* config); +void ReadFromGraphicsMod(UserData* user_data, EditorData* editor_data, + const RuntimeState& runtime_state, + const GraphicsModSystem::Config::GraphicsMod& config, + const std::string& mod_root); + +} // namespace GraphicsModEditor diff --git a/Source/Core/VideoCommon/GraphicsModEditor/EditorTypes.h b/Source/Core/VideoCommon/GraphicsModEditor/EditorTypes.h new file mode 100644 index 0000000000..46098b754f --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/EditorTypes.h @@ -0,0 +1,192 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "Common/CommonTypes.h" + +#include "VideoCommon/AbstractTexture.h" +#include "VideoCommon/Assets/CustomAssetLibrary.h" +#include "VideoCommon/Assets/CustomTextureData.h" +#include "VideoCommon/Assets/DirectFilesystemAssetLibrary.h" +#include "VideoCommon/Assets/MaterialAsset.h" +#include "VideoCommon/Assets/MeshAsset.h" +#include "VideoCommon/Assets/ShaderAsset.h" +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h" +#include "VideoCommon/GraphicsModSystem/Types.h" +#include "VideoCommon/XFMemory.h" + +namespace GraphicsModEditor +{ +struct DrawCallData +{ + GraphicsModSystem::DrawCallID m_id; + std::chrono::steady_clock::time_point m_create_time; + std::chrono::steady_clock::time_point m_last_update_time; + + GraphicsModSystem::DrawData draw_data; +}; + +struct TextureCacheData +{ + GraphicsModSystem::TextureCacheID m_id; + std::chrono::steady_clock::time_point m_create_time; + std::chrono::steady_clock::time_point m_last_load_time; + bool m_active = false; + + GraphicsModSystem::TextureView texture; +}; + +struct LightData +{ + GraphicsModSystem::LightID m_id; + std::chrono::steady_clock::time_point m_create_time; + std::chrono::steady_clock::time_point m_last_update_time; + + int4 m_color; + float4 m_cosatt; + float4 m_distatt; + float4 m_pos; + float4 m_dir; +}; + +struct DrawCallUserData +{ + std::string m_friendly_name; + std::vector m_tag_names; +}; + +struct TextureCacheUserData +{ + std::string m_friendly_name; + std::vector m_tag_names; +}; + +struct LightUserData +{ + std::string m_friendly_name; + std::vector m_tag_names; +}; + +using EditorAssetData = std::variant< + std::unique_ptr, std::unique_ptr, + std::unique_ptr, std::unique_ptr, + std::unique_ptr, + std::unique_ptr, std::unique_ptr>; +enum AssetDataType +{ + Camera, + Material, + RasterMaterial, + Mesh, + PixelShader, + RenderTarget, + Shader, + Texture +}; +struct EditorAsset +{ + VideoCommon::CustomAssetLibrary::AssetID m_asset_id; + std::filesystem::path m_asset_path; + EditorAssetData m_data; + AssetDataType m_data_type; + VideoCommon::CustomAssetLibrary::TimeType m_last_data_write; + VideoCommon::Assets::AssetMap m_asset_map; + bool m_valid = false; +}; + +using SelectableType = + std::variant; + +class EditorAction final : public GraphicsModAction +{ +public: + explicit EditorAction(std::unique_ptr action) : m_action(std::move(action)) {} + void OnDrawStarted(GraphicsModActionData::DrawStarted* draw) override + { + if (m_active) + m_action->OnDrawStarted(draw); + } + void OnEFB(GraphicsModActionData::EFB* efb) override + { + if (m_active) + m_action->OnEFB(efb); + } + void OnXFB() override + { + if (m_active) + m_action->OnXFB(); + } + void OnProjection(GraphicsModActionData::Projection* projection) override + { + if (m_active) + m_action->OnProjection(projection); + } + void OnProjectionAndTexture(GraphicsModActionData::Projection* projection) override + { + if (m_active) + m_action->OnProjectionAndTexture(projection); + } + void OnTextureLoad(GraphicsModActionData::TextureLoad* texture_load) override + { + if (m_active) + m_action->OnTextureLoad(texture_load); + } + void OnTextureCreate(GraphicsModActionData::TextureCreate* texture_create) override + { + if (m_active) + m_action->OnTextureCreate(texture_create); + } + void OnLight(GraphicsModActionData::Light* light) override + { + if (m_active) + m_action->OnLight(light); + } + void OnFrameEnd() override + { + if (m_active) + m_action->OnFrameEnd(); + } + + void SetAllDrawCall(GraphicsModSystem::DrawCallID draw_call) + { + m_draw_call = draw_call; + m_action->SetDrawCall(draw_call); + } + + void DrawImGui() override + { + ImGui::Checkbox("##EmptyCheckbox", &m_active); + m_action->DrawImGui(); + } + + void SerializeToConfig(picojson::object* obj) override + { + if (!obj) [[unlikely]] + return; + + auto& json_obj = *obj; + json_obj["active"] = picojson::value{m_active}; + m_action->SerializeToConfig(&json_obj); + } + + std::string GetFactoryName() const override { return m_action->GetFactoryName(); } + + void SetActive(bool active) { m_active = active; } + +private: + bool m_active = true; + std::unique_ptr m_action; +}; +} // namespace GraphicsModEditor diff --git a/Source/Core/VideoCommon/GraphicsModEditor/MaterialGeneration.cpp b/Source/Core/VideoCommon/GraphicsModEditor/MaterialGeneration.cpp new file mode 100644 index 0000000000..e2d2669608 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/MaterialGeneration.cpp @@ -0,0 +1,424 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModEditor/MaterialGeneration.h" + +#include +#include +#include + +#include +#include +#include + +#include "Common/EnumUtils.h" +#include "Common/FileSearch.h" +#include "Common/FileUtil.h" +#include "Common/JsonUtil.h" +#include "Common/StringUtil.h" + +#include "VideoCommon/Assets/TextureAsset.h" +#include "VideoCommon/Assets/TextureSamplerValue.h" +#include "VideoCommon/GraphicsModEditor/EditorState.h" +#include "VideoCommon/GraphicsModEditor/EditorTypes.h" +#include "VideoCommon/GraphicsModEditor/ShaderGeneration.h" +#include "VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.h" + +namespace GraphicsModEditor +{ +namespace +{ +struct TextureLookup +{ + std::optional draw_call_id; + std::string output_name; + std::vector filenames; +}; +std::vector GenerateLookups(const std::string& filename, std::string* error) +{ + std::vector result; + picojson::value root; + if (!JsonFromFile(filename, &root, error)) + { + return {}; + } + + if (!root.is()) + { + *error = fmt::format("Failed to load '{}', expected root to contain an array", filename); + return {}; + } + + const auto values = root.get(); + for (const auto& value : values) + { + if (!value.is()) + { + *error = fmt::format("Failed to load '{}', value in array is not a json object", filename); + return {}; + } + TextureLookup lookup; + const auto& obj = value.get(); + if (const auto draw_call_id_json = ReadStringFromJson(obj, "draw_call_id")) + { + u64 draw_call_id = 0; + if (!TryParse(*draw_call_id_json, &draw_call_id)) + { + *error = fmt::format("Failed to load '{}', draw call id '{}' is not a number", filename, + *draw_call_id_json); + return {}; + } + lookup.draw_call_id = GraphicsModSystem::DrawCallID{draw_call_id}; + } + const auto output_name = ReadStringFromJson(obj, "output_name"); + if (!output_name) + { + *error = fmt::format("Failed to load '{}', output_name not provided", filename); + return {}; + } + lookup.output_name = *output_name; + + const auto& texture_names_iter = obj.find("texture_names"); + if (texture_names_iter == obj.end()) + { + *error = fmt::format("Failed to load '{}', texture_names not found", filename); + return {}; + } + + if (!texture_names_iter->second.is()) + { + *error = fmt::format("Failed to load '{}', texture_names not an array", filename); + return {}; + } + const auto texture_names_json = texture_names_iter->second.get(); + for (const auto& texture_name_json : texture_names_json) + { + if (texture_name_json.is()) + { + lookup.filenames.push_back(texture_name_json.to_str()); + } + } + + result.push_back(std::move(lookup)); + } + return result; +} +} // namespace +void GenerateMaterials(RasterMaterialGenerationContext* generation_context, std::string* error) +{ + const auto output_dir = std::filesystem::path{generation_context->output_path}; + + std::error_code ec; + std::filesystem::create_directories(output_dir, ec); + if (ec) + { + *error = fmt::format("Failed to create output directory '{}', error was {}", output_dir, ec); + return; + } + + std::map filename_to_asset_id; + std::map>> + texture_hash_to_index_and_filters; + const auto files = + Common::DoFileSearch({std::string{generation_context->input_path}}, {".png", ".dds"}, + generation_context->search_input_recursively); + for (const auto& filename : files) + { + std::string basename; + SplitPath(filename, nullptr, &basename, nullptr); + + const std::filesystem::path filepath{filename}; + + std::string filter_without_image = ""; + std::string filter_stripped = ""; + std::size_t filter_index = 0; + for (const auto& [index, filter] : + generation_context->material_property_index_to_texture_filter) + { + const auto last_curly = filter.find_last_of('}'); + if (last_curly == std::string::npos) + continue; + if (last_curly + 1 >= filter.size()) + continue; + + filter_stripped = filter.substr(last_curly + 1); + if (basename.find(filter_stripped) != std::string::npos) + { + filter_without_image = filter; + filter_index = index; + break; + } + } + + if (filter_without_image.empty()) + continue; + + const std::string texture_hash = ReplaceAll(basename, filter_stripped, ""); + texture_hash_to_index_and_filters[texture_hash].emplace_back(filter_index, + filter_without_image); + + auto texture_asset = + generation_context->state->m_user_data.m_asset_library->GetAssetFromPath(filepath); + + // If the texture doesn't exist, create it + if (!texture_asset) + { + generation_context->state->m_user_data.m_asset_library->AddAsset(filepath); + texture_asset = + generation_context->state->m_user_data.m_asset_library->GetAssetFromPath(filepath); + if (!texture_asset) + { + *error = + fmt::format("Failed to create texture asset from path '{}'", PathToString(filepath)); + return; + } + } + + filename_to_asset_id[basename] = texture_asset->m_asset_id; + } + + for (auto& [draw_call_id, data] : + generation_context->state->m_runtime_data.m_draw_call_id_to_data) + { + auto& action_refs = + generation_context->state->m_user_data.m_draw_call_id_to_actions[draw_call_id]; + if (!action_refs.empty()) + { + // TODO: check if material exists... + continue; + } + + // Skip texture-less meshes for now (we can later support shaders that don't need a texture) + if (data.draw_data.textures.empty()) + continue; + + VideoCommon::RasterMaterialData output_material; + output_material = generation_context->material_template_data; + + for (std::size_t i = 0; i < data.draw_data.textures.size(); i++) + { + const auto& texture = data.draw_data.textures[i]; + + const auto filters_iter = texture_hash_to_index_and_filters.find(texture.hash_name); + if (filters_iter == texture_hash_to_index_and_filters.end()) + continue; + + for (std::size_t filter_i = 0; filter_i < filters_iter->second.size(); filter_i++) + { + auto& pair = filters_iter->second[filter_i]; + const auto filename = + ReplaceAll(pair.second, fmt::format("{{IMAGE_{}}}", i + 1), texture.hash_name); + + const auto filename_iter = filename_to_asset_id.find(filename); + if (filename_iter == filename_to_asset_id.end()) + continue; + + // Get the sampler from the material and update it + auto& texture_sampler = output_material.pixel_textures[pair.first]; + texture_sampler.asset = filename_iter->second; + if (texture_sampler.sampler_origin == + VideoCommon::TextureSamplerValue::SamplerOrigin::TextureHash) + { + texture_sampler.texture_hash = texture.hash_name; + } + } + } + + if (output_material.pixel_textures.empty()) + continue; + + if (std::ranges::all_of( + output_material.pixel_textures, + [](const VideoCommon::TextureSamplerValue& sampler) { return sampler.asset == ""; })) + { + continue; + } + + // Create the material... + picojson::object json_data; + VideoCommon::RasterMaterialData::ToJson(&json_data, output_material); + const std::string draw_call_str = std::to_string(Common::ToUnderlying(draw_call_id)); + const auto material_path = PathToString(output_dir / draw_call_str) + ".rastermaterial"; + if (!JsonToFile(material_path, picojson::value{json_data}, true)) + { + *error = fmt::format("Failed to create json file '{}'", material_path); + return; + } + generation_context->state->m_user_data.m_asset_library->AddAsset(material_path); + const auto material_asset = + generation_context->state->m_user_data.m_asset_library->GetAssetFromPath(material_path); + if (!material_asset) + { + *error = fmt::format("Failed to get asset from path '{}'", material_path); + return; + } + + auto pipeline_action = std::make_unique( + generation_context->state->m_user_data.m_asset_library, material_asset->m_asset_id); + auto action = std::make_unique(std::move(pipeline_action)); + action->SetID(generation_context->state->m_editor_data.m_next_action_id); + action_refs.push_back(action.get()); + generation_context->state->m_user_data.m_actions.push_back(std::move(action)); + generation_context->state->m_editor_data.m_next_action_id++; + } +} + +void GenerateMaterials(MaterialGenerationContext* generation_context, std::string* error) +{ + const std::vector texture_lookups = + GenerateLookups(generation_context->lookup_path, error); + if (!error->empty()) + { + return; + } + + if (texture_lookups.empty()) + { + return; + } + + const auto output_dir = std::filesystem::path{generation_context->output_path}; + + std::error_code ec; + std::filesystem::create_directories(output_dir, ec); + if (ec) + { + *error = fmt::format("Failed to create output directory '{}', error was {}", output_dir, ec); + return; + } + + std::map filename_to_asset_id; + const auto files = + Common::DoFileSearch({std::string{generation_context->input_path}}, {".png", ".dds"}, + generation_context->search_input_recursively); + for (const auto& filename : files) + { + const std::filesystem::path filepath{filename}; + + generation_context->state->m_user_data.m_asset_library->AddAsset(filepath); + const auto texture_asset = + generation_context->state->m_user_data.m_asset_library->GetAssetFromPath(filepath); + if (!texture_asset) + { + *error = fmt::format("Failed to create texture asset from path '{}'", PathToString(filepath)); + return; + } + + std::string basename; + SplitPath(filename, nullptr, &basename, nullptr); + filename_to_asset_id[basename] = texture_asset->m_asset_id; + } + + for (const auto& lookup : texture_lookups) + { + VideoCommon::MaterialData output_material; + output_material = generation_context->material_template_data; + + std::size_t texture_properties_skipped = 0; + for (const auto& [index, filter] : + generation_context->material_property_index_to_texture_filter) + { + bool property_processed = false; + auto& output_material_tex_sampler_val = output_material.properties[index].m_value; + if (const auto prop = + std::get_if(&output_material_tex_sampler_val)) + { + std::string image_name = filter; + for (std::size_t i = 0; i < lookup.filenames.size(); i++) + { + image_name = + ReplaceAll(image_name, fmt::format("{{IMAGE_{}}}", i + 1), lookup.filenames[i]); + } + + if (const auto asset_id_iter = filename_to_asset_id.find(image_name); + asset_id_iter != filename_to_asset_id.end()) + { + prop->asset = asset_id_iter->second; + if (prop->sampler_origin == VideoCommon::TextureSamplerValue::SamplerOrigin::TextureHash) + { + prop->texture_hash = lookup.filenames[0]; + } + property_processed = true; + } + + if (!property_processed) + texture_properties_skipped++; + } + } + + // If we couldn't find any assets, skip this material + if (texture_properties_skipped == + generation_context->material_property_index_to_texture_filter.size()) + { + continue; + } + + picojson::object data; + VideoCommon::MaterialData::ToJson(&data, output_material); + const auto material_path = PathToString(output_dir / lookup.output_name) + ".material"; + if (!JsonToFile(material_path, picojson::value{data}, true)) + { + *error = fmt::format("Failed to create json file '{}'", material_path); + return; + } + generation_context->state->m_user_data.m_asset_library->AddAsset(material_path); + const auto material_asset = + generation_context->state->m_user_data.m_asset_library->GetAssetFromPath(material_path); + if (!material_asset) + { + *error = fmt::format("Failed to get asset from path '{}'", material_path); + return; + } + + if (lookup.draw_call_id) + { + auto pipeline_action = std::make_unique( + generation_context->state->m_user_data.m_asset_library, material_asset->m_asset_id); + auto action = std::make_unique(std::move(pipeline_action)); + action->SetID(generation_context->state->m_editor_data.m_next_action_id); + + auto& action_refs = + generation_context->state->m_user_data.m_draw_call_id_to_actions[*lookup.draw_call_id]; + action_refs.push_back(action.get()); + + generation_context->state->m_user_data.m_actions.push_back(std::move(action)); + generation_context->state->m_editor_data.m_next_action_id++; + } + } +} + +EditorAsset* GenerateMaterial(std::string_view name, const std::filesystem::path& output_path, + const VideoCommon::RasterMaterialData& metadata_template, + const ShaderGenerationContext& shader_context, + GraphicsModEditor::EditorAssetSource* source) +{ + const auto shader_asset = GenerateShader(output_path, shader_context, source); + if (shader_asset == nullptr) + { + return nullptr; + } + + VideoCommon::RasterMaterialData material = metadata_template; + material.shader_asset = shader_asset->m_asset_id; + + picojson::object obj; + VideoCommon::RasterMaterialData::ToJson(&obj, material); + + return GenerateMaterial(name, output_path, picojson::value{obj}.serialize(), source); +} + +EditorAsset* GenerateMaterial(std::string_view name, const std::filesystem::path& output_path, + std::string_view metadata_src, + GraphicsModEditor::EditorAssetSource* source) +{ + const auto metadata_path = output_path / name; + if (!File::WriteStringToFile(PathToString(metadata_path), metadata_src)) + { + return nullptr; + } + + source->AddAsset(metadata_path); + return source->GetAssetFromPath(metadata_path); +} + +} // namespace GraphicsModEditor diff --git a/Source/Core/VideoCommon/GraphicsModEditor/MaterialGeneration.h b/Source/Core/VideoCommon/GraphicsModEditor/MaterialGeneration.h new file mode 100644 index 0000000000..1a2cf5e52e --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/MaterialGeneration.h @@ -0,0 +1,48 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#include "VideoCommon/Assets/MaterialAsset.h" + +namespace GraphicsModEditor +{ +struct EditorAsset; +class EditorAssetSource; +struct EditorState; +struct ShaderGenerationContext; + +struct MaterialGenerationContext +{ + EditorState* state; + std::string lookup_path; + std::string input_path; + std::string output_path; + VideoCommon::MaterialData material_template_data; + std::map material_property_index_to_texture_filter; + bool search_input_recursively = true; +}; +struct RasterMaterialGenerationContext +{ + EditorState* state; + std::string lookup_path; + std::string input_path; + std::string output_path; + VideoCommon::RasterMaterialData material_template_data; + std::map material_property_index_to_texture_filter; + bool search_input_recursively = true; +}; +void GenerateMaterials(MaterialGenerationContext* generation_context, std::string* error); +void GenerateMaterials(RasterMaterialGenerationContext* generation_context, std::string* error); +EditorAsset* GenerateMaterial(std::string_view name, const std::filesystem::path& output_path, + const VideoCommon::RasterMaterialData& metadata_template, + const ShaderGenerationContext& shader_context, + GraphicsModEditor::EditorAssetSource* source); +EditorAsset* GenerateMaterial(std::string_view name, const std::filesystem::path& output_path, + std::string_view metadata, + GraphicsModEditor::EditorAssetSource* source); +} // namespace GraphicsModEditor diff --git a/Source/Core/VideoCommon/GraphicsModEditor/Panels/ActiveTargetsPanel.cpp b/Source/Core/VideoCommon/GraphicsModEditor/Panels/ActiveTargetsPanel.cpp new file mode 100644 index 0000000000..4f9ef9ce2c --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/Panels/ActiveTargetsPanel.cpp @@ -0,0 +1,775 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModEditor/Panels/ActiveTargetsPanel.h" + +#include +#include + +#include +#include +#include + +#include "Common/EnumUtils.h" + +#include "VideoCommon/GraphicsModEditor/Controls/MeshExtractWindow.h" +#include "VideoCommon/GraphicsModEditor/EditorEvents.h" +#include "VideoCommon/GraphicsModEditor/EditorTypes.h" +#include "VideoCommon/GraphicsModSystem/Runtime/Actions/CustomMeshAction.h" +#include "VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.h" +#include "VideoCommon/GraphicsModSystem/Runtime/Actions/ModifyLight.h" +#include "VideoCommon/GraphicsModSystem/Runtime/Actions/RelativeCameraAction.h" +#include "VideoCommon/GraphicsModSystem/Runtime/Actions/SkipAction.h" +#include "VideoCommon/GraphicsModSystem/Runtime/Actions/TransformAction.h" +#include "VideoCommon/Present.h" +#include "VideoCommon/VideoEvents.h" + +namespace GraphicsModEditor::Panels +{ +ActiveTargetsPanel::ActiveTargetsPanel(EditorState& state) : m_state(state) +{ + m_selection_event = EditorEvents::ItemsSelectedEvent::Register( + [this](const std::set& selected_targets) { + if (selected_targets.size() == 1 && + std::holds_alternative(*selected_targets.begin())) + { + m_selected_nodes.clear(); + } + }, + "EditorActiveTargetsPanelSelection"); +} + +void ActiveTargetsPanel::DrawImGui() +{ + const float filter_window_size_collapsed = 30; + const float filter_window_size_expanded = 415; + // Set the active target panel first use size and position + const ImGuiViewport* main_viewport = ImGui::GetMainViewport(); + u32 default_window_height = g_presenter->GetTargetRectangle().GetHeight() - + ((float)g_presenter->GetTargetRectangle().GetHeight() * 0.1); + u32 default_window_width = + ((float)g_presenter->GetTargetRectangle().GetWidth() * 0.15) + filter_window_size_collapsed; + ImGui::SetNextWindowPos(ImVec2(main_viewport->WorkPos.x + default_window_width / 4, + main_viewport->WorkPos.y + + ((float)g_presenter->GetTargetRectangle().GetHeight() * 0.05)), + ImGuiCond_FirstUseEver); + + if (m_filter_menu_toggled) + { + ImGui::SetNextWindowSize(ImVec2{m_next_window_width, m_next_window_height}); + m_filter_menu_toggled = false; + } + else + { + ImGui::SetNextWindowSize(ImVec2(0, default_window_height), ImGuiCond_FirstUseEver); + } + + m_selection_list_changed = false; + ImGui::Begin("Scene Panel", nullptr, ImGuiWindowFlags_NoResize); + const auto window_width = ImGui::GetWindowWidth(); + const auto window_height = ImGui::GetWindowHeight(); + + std::vector xfbs; + + if (m_freeze_scene_details) + { + if (ImGui::Button("Unfreeze Scene List")) + m_freeze_scene_details = false; + } + else + { + ImGui::SetItemTooltip("All existing scene elements will stay and no new ones will be added, " + "making it easier to view some content"); + if (ImGui::Button("Freeze Scene List")) + m_freeze_scene_details = true; + } + if (m_freeze_scene_details && !m_frozen_xfbs_presented.empty()) + { + for (const auto& xfb_presented : m_frozen_xfbs_presented) + { + xfbs.push_back(&m_frozen_xfb_to_data[xfb_presented]); + } + } + else + { + for (const auto& xfb_presented : m_state.m_runtime_data.m_xfbs_presented) + { + xfbs.push_back(&m_state.m_runtime_data.m_xfb_to_data[xfb_presented]); + } + + if (m_freeze_scene_details) + { + m_frozen_xfbs_presented = m_state.m_runtime_data.m_xfbs_presented; + m_frozen_xfb_to_data = m_state.m_runtime_data.m_xfb_to_data; + } + } + + ImGui::BeginChild("##SceneList", ImVec2{400, 0}, ImGuiChildFlags_None); + ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_None; + if (ImGui::BeginTabBar("SceneTabs", tab_bar_flags)) + { + if (ImGui::BeginTabItem("Objects")) + { + DrawCallPanel(xfbs); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Textures")) + { + TexturesPanel(xfbs); + ImGui::EndTabItem(); + } + /*if (ImGui::BeginTabItem("Lights")) + { + LightPanel(xfbs); + ImGui::EndTabItem(); + }*/ + ImGui::EndTabBar(); + } + ImGui::EndChild(); + ImGui::SameLine(); + + if (m_filter_menu_active) + { + const ImVec2 size{filter_window_size_expanded, 0}; + ImGui::BeginChild("##FilterMenu", size, ImGuiChildFlags_None); + } + else + { + const ImVec2 size{filter_window_size_collapsed, 0}; + ImGui::BeginChild("##FilterMenu", size, ImGuiChildFlags_None); + } + + if (m_filter_menu_active) + { + if (ImGui::Button("<<")) + { + m_filter_menu_toggled = true; + m_filter_menu_active = false; + + m_next_window_width = + window_width - filter_window_size_expanded + filter_window_size_collapsed; + m_next_window_height = window_height; + } + + if (ImGui::BeginTable("FilterTable", 2)) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Name"); + ImGui::TableNextColumn(); + ImGui::InputText("##FilterName", &m_drawcall_filter_context.text); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Texture Type"); + ImGui::TableNextColumn(); + ImGui::BeginGroup(); + + auto rb_texture = [&](std::string_view text, + DrawCallFilterContext::TextureFilter texture_filter) { + if (ImGui::RadioButton(text.data(), + m_drawcall_filter_context.texture_filter == texture_filter)) + { + m_drawcall_filter_context.texture_filter = texture_filter; + } + }; + + rb_texture("Any##anytexture", DrawCallFilterContext::TextureFilter::Any); + rb_texture("Texture Only", DrawCallFilterContext::TextureFilter::TextureOnly); + rb_texture("EFB Only", DrawCallFilterContext::TextureFilter::EFBOnly); + rb_texture("EFB and Textures", DrawCallFilterContext::TextureFilter::Both); + rb_texture("None", DrawCallFilterContext::TextureFilter::None); + ImGui::EndGroup(); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Projection Type"); + ImGui::TableNextColumn(); + ImGui::BeginGroup(); + + auto rb_projection = [&](std::string_view text, + DrawCallFilterContext::ProjectionFilter projection_filter) { + if (ImGui::RadioButton(text.data(), + m_drawcall_filter_context.projection_filter == projection_filter)) + { + m_drawcall_filter_context.projection_filter = projection_filter; + } + }; + + rb_projection("Any##anyprojection", DrawCallFilterContext::ProjectionFilter::Any); + rb_projection("Orthographic", DrawCallFilterContext::ProjectionFilter::Orthographic); + rb_projection("Perspective", DrawCallFilterContext::ProjectionFilter::Perspective); + ImGui::EndGroup(); + + ImGui::EndTable(); + } + + if (m_draw_call_filter_active) + { + if (ImGui::Button("Cancel")) + { + m_draw_call_filter_active = false; + } + } + else + { + if (ImGui::Button("Filter")) + { + m_draw_call_filter_active = true; + } + } + } + else + { + if (ImGui::Button(">>")) + { + m_filter_menu_toggled = true; + m_filter_menu_active = true; + + m_next_window_width = + window_width - filter_window_size_collapsed + filter_window_size_expanded; + m_next_window_height = window_height; + } + } + ImGui::EndChild(); + + ImGui::End(); + + if (m_selection_list_changed) + { + EditorEvents::ItemsSelectedEvent::Trigger(m_selected_nodes); + } + + if (m_open_mesh_dump_export_window) + { + if (Controls::ShowMeshExtractWindow(m_state.m_scene_dumper, m_last_mesh_dump_request)) + { + m_open_mesh_dump_export_window = false; + m_last_mesh_dump_request = {}; + } + } +} + +void ActiveTargetsPanel::DrawCallPanel( + const std::vector& xfbs) +{ + static constexpr ImGuiTreeNodeFlags base_flags = ImGuiTreeNodeFlags_OpenOnArrow | + ImGuiTreeNodeFlags_OpenOnDoubleClick | + ImGuiTreeNodeFlags_SpanAvailWidth; + + std::unordered_set draw_calls_seen; + for (const auto xfb_data : xfbs) + { + for (const auto& draw_call_id_with_time : xfb_data->m_draw_call_ids) + { + const auto draw_call_id = draw_call_id_with_time.draw_call_id; + if (draw_calls_seen.contains(draw_call_id)) + continue; + if (m_draw_call_filter_active && + !DoesDrawCallMatchFilter(m_drawcall_filter_context, m_state, draw_call_id)) + { + continue; + } + draw_calls_seen.insert(draw_call_id); + const auto target_actions_iter = + m_state.m_user_data.m_draw_call_id_to_actions.find(draw_call_id); + + ImGuiTreeNodeFlags node_flags; + + if (target_actions_iter == m_state.m_user_data.m_draw_call_id_to_actions.end()) + node_flags = ImGuiTreeNodeFlags_Leaf; + else + node_flags = base_flags; + + if (m_selected_nodes.contains(draw_call_id)) + node_flags |= ImGuiTreeNodeFlags_Selected; + + ImGui::Image(*m_state.m_editor_data.m_name_to_texture["filled_cube"].get(), ImVec2{25, 25}); + ImGui::SameLine(); + + ImGui::SetNextItemOpen(m_open_draw_call_nodes.contains(draw_call_id)); + const std::string id = fmt::to_string(Common::ToUnderlying(draw_call_id)); + + std::string_view name = id; + if (const auto iter = m_state.m_user_data.m_draw_call_id_to_user_data.find(draw_call_id); + iter != m_state.m_user_data.m_draw_call_id_to_user_data.end()) + { + if (!iter->second.m_friendly_name.empty()) + name = iter->second.m_friendly_name; + } + const bool node_open = ImGui::TreeNodeEx(id.c_str(), node_flags, "%s", name.data()); + + bool ignore_target_context_menu = false; + HandleSelectionEvent(draw_call_id); + + // Normally we would use 'BeginPopupContextItem' but unfortunately we can't logically do this + // after handling the node state because it gets the _last_ item clicked + const bool potential_popup = + ImGui::IsItemHovered() && ImGui::IsMouseReleased(ImGuiMouseButton_Right); + if (!node_open) + { + m_open_draw_call_nodes.erase(draw_call_id); + } + else + { + m_open_draw_call_nodes.insert(draw_call_id); + if (target_actions_iter != m_state.m_user_data.m_draw_call_id_to_actions.end()) + { + std::vector actions_to_delete; + for (const auto& action : target_actions_iter->second) + { + node_flags = ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen | + ImGuiTreeNodeFlags_SpanAvailWidth; + + if (m_selected_nodes.contains(action)) + node_flags |= ImGuiTreeNodeFlags_Selected; + const std::string action_name = + fmt::format("{}-{}", action->GetFactoryName(), action->GetID()); + ImGui::TreeNodeEx(action_name.c_str(), node_flags, "%s", action_name.c_str()); + HandleSelectionEvent(action); + + if (ImGui::BeginPopupContextItem()) + { + ignore_target_context_menu = true; + if (ImGui::Selectable("Delete")) + { + actions_to_delete.push_back(action); + } + ImGui::EndPopup(); + } + } + + for (const auto& action_to_delete : actions_to_delete) + { + // Removal from reference memory container + std::erase_if(target_actions_iter->second, [action_to_delete](auto& action_own) { + return action_own == action_to_delete; + }); + if (target_actions_iter->second.empty()) + { + m_state.m_user_data.m_draw_call_id_to_actions.erase(target_actions_iter); + } + + // Removal from owning memory container + std::erase_if(m_state.m_user_data.m_actions, [action_to_delete](auto&& action) { + return action.get() == action_to_delete; + }); + + if (m_selected_nodes.erase(action_to_delete) > 0) + { + m_selection_list_changed = true; + } + } + } + ImGui::TreePop(); + } + + if (!ignore_target_context_menu) + { + if (potential_popup) + { + if (!ImGui::IsPopupOpen(id.data())) + { + ImGui::OpenPopup(id.data()); + } + } + if (ImGui::BeginPopup(id.data())) + { + if (ImGui::BeginMenu("Add Action")) + { + if (ImGui::MenuItem("Transform")) + { + auto action = std::make_unique(std::make_unique()); + action->SetID(m_state.m_editor_data.m_next_action_id); + action->SetDrawCall(draw_call_id); + + auto& action_refs = m_state.m_user_data.m_draw_call_id_to_actions[draw_call_id]; + action_refs.push_back(action.get()); + + m_state.m_user_data.m_actions.push_back(std::move(action)); + m_open_draw_call_nodes.insert(draw_call_id); + m_state.m_editor_data.m_next_action_id++; + } + + if (ImGui::MenuItem("Skip Draw")) + { + auto action = std::make_unique(std::make_unique()); + action->SetID(m_state.m_editor_data.m_next_action_id); + action->SetAllDrawCall(draw_call_id); + + auto& action_refs = m_state.m_user_data.m_draw_call_id_to_actions[draw_call_id]; + action_refs.push_back(action.get()); + + m_state.m_user_data.m_actions.push_back(std::move(action)); + m_open_draw_call_nodes.insert(draw_call_id); + m_state.m_editor_data.m_next_action_id++; + } + + if (ImGui::MenuItem("Custom Mesh")) + { + auto action = std::make_unique( + std::make_unique(m_state.m_user_data.m_asset_library)); + action->SetID(m_state.m_editor_data.m_next_action_id); + action->SetAllDrawCall(draw_call_id); + + auto& action_refs = m_state.m_user_data.m_draw_call_id_to_actions[draw_call_id]; + action_refs.push_back(action.get()); + + m_state.m_user_data.m_actions.push_back(std::move(action)); + m_open_draw_call_nodes.insert(draw_call_id); + m_state.m_editor_data.m_next_action_id++; + } + + if (ImGui::MenuItem("Custom Pipeline")) + { + auto action = std::make_unique( + CustomPipelineAction::Create(m_state.m_user_data.m_asset_library)); + action->SetID(m_state.m_editor_data.m_next_action_id); + action->SetAllDrawCall(draw_call_id); + + auto& action_refs = m_state.m_user_data.m_draw_call_id_to_actions[draw_call_id]; + action_refs.push_back(action.get()); + + m_state.m_user_data.m_actions.push_back(std::move(action)); + m_open_draw_call_nodes.insert(draw_call_id); + m_state.m_editor_data.m_next_action_id++; + } + + if (ImGui::MenuItem("Camera")) + { + auto action = + std::make_unique(std::make_unique()); + action->SetID(m_state.m_editor_data.m_next_action_id); + action->SetAllDrawCall(draw_call_id); + + auto& action_refs = m_state.m_user_data.m_draw_call_id_to_actions[draw_call_id]; + action_refs.push_back(action.get()); + + m_state.m_user_data.m_actions.push_back(std::move(action)); + m_open_draw_call_nodes.insert(draw_call_id); + m_state.m_editor_data.m_next_action_id++; + } + ImGui::EndMenu(); + } + + if (ImGui::BeginMenu("Export")) + { + if (ImGui::MenuItem("Mesh")) + { + if (!m_open_mesh_dump_export_window) + { + m_last_mesh_dump_request.m_draw_call_ids.insert(draw_call_id); + } + m_open_mesh_dump_export_window = true; + } + if (ImGui::MenuItem("Shaders")) + { + m_state.m_editor_data.m_export_dolphin_material_draw_call = draw_call_id; + } + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Create")) + { + if (ImGui::MenuItem("Material")) + { + m_state.m_editor_data.m_create_material_draw_call = draw_call_id; + } + ImGui::EndMenu(); + } + ImGui::EndPopup(); + } + } + } + } +} + +void ActiveTargetsPanel::TexturesPanel( + const std::vector& xfbs) +{ + static constexpr ImGuiTreeNodeFlags base_flags = ImGuiTreeNodeFlags_OpenOnArrow | + ImGuiTreeNodeFlags_OpenOnDoubleClick | + ImGuiTreeNodeFlags_SpanAvailWidth; + + for (const auto xfb_data : xfbs) + { + for (const auto& texture_cache_id : xfb_data->m_texture_cache_ids) + { + const auto& runtime_data = + m_state.m_runtime_data.m_texture_cache_id_to_data[texture_cache_id]; + + // TODO: don't skip, just present differently... + // if (runtime_data.texture.texture_type != GraphicsModSystem::TextureType::EFB) + // continue; + if (!runtime_data.m_active) + continue; + + const auto target_actions_iter = + m_state.m_user_data.m_texture_cache_id_to_actions.find(texture_cache_id); + + ImGuiTreeNodeFlags node_flags; + + if (target_actions_iter == m_state.m_user_data.m_texture_cache_id_to_actions.end()) + node_flags = ImGuiTreeNodeFlags_Leaf; + else + node_flags = base_flags; + + if (m_selected_nodes.contains(texture_cache_id)) + node_flags |= ImGuiTreeNodeFlags_Selected; + + ImGui::Image(*m_state.m_editor_data.m_name_to_texture["filled_cube"].get(), ImVec2{25, 25}); + ImGui::SameLine(); + + ImGui::SetNextItemOpen(m_open_texture_call_nodes.contains(texture_cache_id)); + std::string_view name = texture_cache_id; + if (const auto iter = + m_state.m_user_data.m_texture_cache_id_to_user_data.find(texture_cache_id); + iter != m_state.m_user_data.m_texture_cache_id_to_user_data.end()) + { + if (!iter->second.m_friendly_name.empty()) + name = iter->second.m_friendly_name; + } + const bool node_open = + ImGui::TreeNodeEx(texture_cache_id.c_str(), node_flags, "%s", name.data()); + + bool ignore_target_context_menu = false; + HandleSelectionEvent(texture_cache_id); + if (!node_open) + { + m_open_texture_call_nodes.erase(texture_cache_id); + } + else + { + m_open_texture_call_nodes.insert(texture_cache_id); + if (target_actions_iter != m_state.m_user_data.m_texture_cache_id_to_actions.end()) + { + std::vector actions_to_delete; + for (const auto& action : target_actions_iter->second) + { + node_flags = ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen | + ImGuiTreeNodeFlags_SpanAvailWidth; + + if (m_selected_nodes.contains(action)) + node_flags |= ImGuiTreeNodeFlags_Selected; + const std::string action_name = + fmt::format("{}-{}", action->GetFactoryName(), action->GetID()); + ImGui::TreeNodeEx(action_name.c_str(), node_flags, "%s", action_name.c_str()); + HandleSelectionEvent(action); + + if (ImGui::BeginPopupContextItem()) + { + ignore_target_context_menu = true; + if (ImGui::Selectable("Delete")) + { + actions_to_delete.push_back(action); + } + ImGui::EndPopup(); + } + } + + for (const auto& action_to_delete : actions_to_delete) + { + // Removal from reference memory container + std::erase_if(target_actions_iter->second, [action_to_delete](auto& action_own) { + return action_own == action_to_delete; + }); + if (target_actions_iter->second.empty()) + { + m_state.m_user_data.m_texture_cache_id_to_actions.erase(target_actions_iter); + } + + // Removal from owning memory container + std::erase_if(m_state.m_user_data.m_actions, [action_to_delete](auto&& action) { + return action.get() == action_to_delete; + }); + + if (m_selected_nodes.erase(action_to_delete) > 0) + { + m_selection_list_changed = true; + } + } + } + ImGui::TreePop(); + } + + if (!ignore_target_context_menu && + runtime_data.texture.texture_type == GraphicsModSystem::TextureType::EFB) + { + if (ImGui::BeginPopupContextItem(texture_cache_id.c_str())) + { + if (ImGui::MenuItem("Skip Draw")) + { + auto action = std::make_unique(std::make_unique()); + action->SetID(m_state.m_editor_data.m_next_action_id); + + auto& action_refs = m_state.m_user_data.m_texture_cache_id_to_actions[texture_cache_id]; + action_refs.push_back(action.get()); + + m_state.m_user_data.m_actions.push_back(std::move(action)); + m_open_texture_call_nodes.insert(texture_cache_id); + m_state.m_editor_data.m_next_action_id++; + } + ImGui::EndPopup(); + } + ImGui::OpenPopupOnItemClick(texture_cache_id.c_str(), ImGuiPopupFlags_MouseButtonRight); + } + } + } +} + +void ActiveTargetsPanel::LightPanel( + const std::vector& xfbs) +{ + static constexpr ImGuiTreeNodeFlags base_flags = ImGuiTreeNodeFlags_OpenOnArrow | + ImGuiTreeNodeFlags_OpenOnDoubleClick | + ImGuiTreeNodeFlags_SpanAvailWidth; + for (const auto xfb_data : xfbs) + { + for (const auto light_id : xfb_data->m_light_ids) + { + const auto& runtime_data = m_state.m_runtime_data.m_light_id_to_data[light_id]; + const auto& user_data = m_state.m_user_data.m_light_id_to_user_data[light_id]; + const auto target_actions_iter = + m_state.m_user_data.m_light_id_to_reference_actions.find(light_id); + + ImGuiTreeNodeFlags node_flags; + + if (target_actions_iter == m_state.m_user_data.m_light_id_to_reference_actions.end()) + node_flags = ImGuiTreeNodeFlags_Leaf; + else + node_flags = base_flags; + + if (m_selected_nodes.contains(light_id)) + node_flags |= ImGuiTreeNodeFlags_Selected; + + // ImGui::SetNextItemWidth(25.0f); + ImGui::Image(*m_state.m_editor_data.m_name_to_texture["filled_cube"].get(), ImVec2{25, 25}); + ImGui::SameLine(); + + ImGui::SetNextItemOpen(m_open_light_nodes.contains(light_id)); + const std::string id = fmt::to_string(Common::ToUnderlying(light_id)); + std::string_view name; + if (user_data.m_friendly_name.empty()) + name = id; + else + name = user_data.m_friendly_name; + const bool node_open = ImGui::TreeNodeEx(id.c_str(), node_flags, "%s", name.data()); + + bool ignore_target_context_menu = false; + HandleSelectionEvent(light_id); + if (!node_open) + { + m_open_light_nodes.erase(light_id); + } + else + { + m_open_light_nodes.insert(light_id); + std::vector actions_to_delete; + for (const auto& action : target_actions_iter->second) + { + node_flags = ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen | + ImGuiTreeNodeFlags_SpanAvailWidth; + + if (m_selected_nodes.contains(action)) + node_flags |= ImGuiTreeNodeFlags_Selected; + const std::string action_name = + fmt::format("{}-{}", action->GetFactoryName(), action->GetID()); + ImGui::TreeNodeEx(action_name.c_str(), node_flags, "%s", action_name.c_str()); + HandleSelectionEvent(action); + + if (ImGui::BeginPopupContextItem()) + { + ignore_target_context_menu = true; + if (ImGui::Selectable("Delete")) + { + actions_to_delete.push_back(action); + } + ImGui::EndPopup(); + } + } + + for (const auto& action_to_delete : actions_to_delete) + { + // Removal from reference memory container + std::erase_if(target_actions_iter->second, [action_to_delete](auto& action_own) { + return action_own == action_to_delete; + }); + if (target_actions_iter->second.empty()) + { + m_state.m_user_data.m_light_id_to_reference_actions.erase(target_actions_iter); + } + + // Removal from owning memory container + std::erase_if(m_state.m_user_data.m_actions, [action_to_delete](auto&& action) { + return action.get() == action_to_delete; + }); + + if (m_selected_nodes.erase(action_to_delete) > 0) + { + m_selection_list_changed = true; + } + } + ImGui::TreePop(); + } + + if (!ignore_target_context_menu) + { + if (ImGui::BeginPopupContextItem(id.c_str())) + { + if (ImGui::MenuItem("Skip")) + { + auto action = std::make_unique(std::make_unique()); + action->SetID(m_state.m_editor_data.m_next_action_id); + + auto& action_refs = m_state.m_user_data.m_light_id_to_reference_actions[light_id]; + action_refs.push_back(action.get()); + + m_state.m_user_data.m_actions.push_back(std::move(action)); + m_open_light_nodes.insert(light_id); + m_state.m_editor_data.m_next_action_id++; + } + + if (ImGui::MenuItem("Modify light")) + { + float4 color_as_editor; + color_as_editor[0] = runtime_data.m_color[0] / 255.0; + color_as_editor[1] = runtime_data.m_color[1] / 255.0; + color_as_editor[2] = runtime_data.m_color[2] / 255.0; + color_as_editor[3] = runtime_data.m_color[3] / 255.0; + auto action = std::make_unique(std::make_unique( + color_as_editor, runtime_data.m_cosatt, runtime_data.m_distatt, runtime_data.m_pos, + runtime_data.m_dir)); + action->SetID(m_state.m_editor_data.m_next_action_id); + + auto& action_refs = m_state.m_user_data.m_light_id_to_reference_actions[light_id]; + action_refs.push_back(action.get()); + + m_state.m_user_data.m_actions.push_back(std::move(action)); + m_open_light_nodes.insert(light_id); + m_state.m_editor_data.m_next_action_id++; + } + ImGui::EndPopup(); + } + ImGui::OpenPopupOnItemClick(id.c_str(), ImGuiPopupFlags_MouseButtonRight); + } + } + } +} + +void ActiveTargetsPanel::HandleSelectionEvent(SelectableType selectable) +{ + const bool item_clicked = ImGui::IsItemClicked(ImGuiMouseButton_::ImGuiMouseButton_Left); + const bool key_transition = (ImGui::IsWindowFocused() && ImGui::IsItemFocused() && + (ImGui::IsKeyDown(ImGuiKey::ImGuiKey_DownArrow) || + ImGui::IsKeyDown(ImGuiKey::ImGuiKey_UpArrow))); + if (item_clicked || key_transition) + { + if (!ImGui::GetIO().KeyCtrl) + m_selected_nodes.clear(); + const auto [it, inserted] = m_selected_nodes.insert(selectable); + if (inserted) + { + m_selection_list_changed = true; + } + } +} +} // namespace GraphicsModEditor::Panels diff --git a/Source/Core/VideoCommon/GraphicsModEditor/Panels/ActiveTargetsPanel.h b/Source/Core/VideoCommon/GraphicsModEditor/Panels/ActiveTargetsPanel.h new file mode 100644 index 0000000000..f930ab0dcb --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/Panels/ActiveTargetsPanel.h @@ -0,0 +1,62 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#include "Common/HookableEvent.h" +#include "VideoCommon/GraphicsModEditor/EditorFilter.h" +#include "VideoCommon/GraphicsModEditor/EditorState.h" +#include "VideoCommon/GraphicsModEditor/EditorTypes.h" +#include "VideoCommon/GraphicsModSystem/Types.h" + +namespace GraphicsModEditor::Panels +{ +class ActiveTargetsPanel +{ +public: + explicit ActiveTargetsPanel(EditorState& state); + + // Renders ImGui windows to the currently-bound framebuffer. + void DrawImGui(); + +private: + void DrawCallPanel(const std::vector& xfbs); + void TexturesPanel(const std::vector& xfbs); + void LightPanel(const std::vector& xfbs); + + void HandleSelectionEvent(SelectableType selectable); + Common::EventHook m_selection_event; + + EditorState& m_state; + + // Track open nodes + std::set m_open_draw_call_nodes; + std::set> m_open_texture_call_nodes; + std::set m_open_light_nodes; + + // Selected nodes + std::set m_selected_nodes; + bool m_selection_list_changed; + + // Mesh extraction window details + SceneDumper::RecordingRequest m_last_mesh_dump_request; + bool m_open_mesh_dump_export_window = false; + + // Freeze details + bool m_freeze_scene_details = false; + std::map> m_frozen_xfb_to_data; + std::vector m_frozen_xfbs_presented; + + // Filter menu + bool m_filter_menu_toggled = false; + bool m_filter_menu_active = false; + float m_next_window_width = 0; + float m_next_window_height = 0; + bool m_draw_call_filter_active = false; + DrawCallFilterContext m_drawcall_filter_context; +}; +} // namespace GraphicsModEditor::Panels diff --git a/Source/Core/VideoCommon/GraphicsModEditor/Panels/AssetBrowserPanel.cpp b/Source/Core/VideoCommon/GraphicsModEditor/Panels/AssetBrowserPanel.cpp new file mode 100644 index 0000000000..2ed38c527b --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/Panels/AssetBrowserPanel.cpp @@ -0,0 +1,537 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModEditor/Panels/AssetBrowserPanel.h" + +#include +#include + +#include +#include + +#include "Common/CommonTypes.h" +#include "Common/FileUtil.h" +#include "Common/StringUtil.h" +#include "Common/VariantUtil.h" +#include "Core/System.h" +#include "VideoCommon/Assets/CustomAssetLoader.h" +#include "VideoCommon/GraphicsModEditor/Controls/MaterialGenerateWindow.h" +#include "VideoCommon/GraphicsModEditor/Controls/MeshImportWindow.h" +#include "VideoCommon/GraphicsModEditor/EditorEvents.h" +#include "VideoCommon/GraphicsModEditor/MaterialGeneration.h" +#include "VideoCommon/GraphicsModEditor/RenderTargetGeneration.h" +#include "VideoCommon/GraphicsModEditor/ShaderGeneration.h" +#include "VideoCommon/Present.h" + +namespace +{ +std::string FindNewName(const std::string& base_name, const std::string& extension, + const std::filesystem::path& base_path) +{ + u32 index = 1; + std::string new_name = base_name + extension; + while (std::filesystem::exists(base_path / new_name)) + { + new_name = fmt::format("{} {}{}", base_name, index, extension); + index++; + } + + return new_name; +} +} // namespace + +namespace GraphicsModEditor::Panels +{ +AssetBrowserPanel::AssetBrowserPanel(EditorState& state) : m_state(state) +{ + ResetCurrentPath(); + + m_browse_event = EditorEvents::JumpToAssetInBrowserEvent::Register( + [this](const VideoCommon::CustomAssetLibrary::AssetID& asset_id) { BrowseEvent(asset_id); }, + "EditorAssetBrowserPanel"); +} + +void AssetBrowserPanel::ResetCurrentPath() +{ + m_current_path = m_state.m_user_data.m_current_mod_path; +} + +void AssetBrowserPanel::DrawImGui() +{ + const ImGuiViewport* main_viewport = ImGui::GetMainViewport(); + u32 default_window_height = g_presenter->GetTargetRectangle().GetHeight() * 0.2; + u32 default_window_width = g_presenter->GetTargetRectangle().GetWidth() * 0.95; + ImGui::SetNextWindowPos(ImVec2(main_viewport->WorkPos.x + default_window_width * 0.1, + main_viewport->WorkPos.y + + g_presenter->GetTargetRectangle().GetHeight() - + default_window_height), + ImGuiCond_FirstUseEver); + ImGui::SetNextWindowSize(ImVec2(default_window_width, default_window_height), + ImGuiCond_FirstUseEver); + + ImGui::Begin("Asset Browser Panel"); + + std::error_code ec; + auto dir_iter = std::filesystem::directory_iterator(m_current_path, ec); + if (ec) + { + ImGui::Text( + "%s", + fmt::format("Error viewing current directory '{}'", PathToString(m_current_path)).c_str()); + ImGui::End(); + return; + } + std::vector directory_entries; + for (const auto& entry : dir_iter) + { + directory_entries.push_back(entry.path()); + } + + // Split the current path into pieces from the root + const auto relative_path = + std::filesystem::relative(m_current_path, m_state.m_user_data.m_current_mod_path, ec); + if (ec) + { + ImGui::Text("%s", fmt::format("Error getting relative path from directory '{}' to root '{}'", + PathToString(m_current_path), + PathToString(m_state.m_user_data.m_current_mod_path)) + .c_str()); + ImGui::End(); + return; + } + + std::filesystem::path full_path = m_state.m_user_data.m_current_mod_path; + if (ImGui::Button("Assets")) + { + m_current_path = full_path; + } + if (relative_path != std::filesystem::path{"."}) + { + ImGui::SameLine(); + ImGui::Text(" > "); + + const std::vector path_pieces(relative_path.begin(), + relative_path.end()); + for (const auto& piece : path_pieces) + { + ImGui::SameLine(); + full_path /= piece; + if (ImGui::Button(PathToString(piece).c_str())) + { + m_current_path = full_path; + } + ImGui::SameLine(); + ImGui::Text(" > "); + } + } + + const float search_size = 200.0f; + const float search_padding = 50.0f; + if (ImGui::GetWindowSize().x > (search_size + search_padding)) + { + const ImVec2 dummy_size = + ImVec2(ImGui::GetWindowSize().x - search_size - search_padding, ImGui::GetTextLineHeight()); + ImGui::Dummy(dummy_size); + ImGui::SameLine(); + } + ImGui::SetNextItemWidth(search_size); + ImGui::InputTextWithHint("##", "Search...", &m_filter_text); + + const u32 column_count = + static_cast(ImGui::GetContentRegionAvail().x) / (thumbnail_size + padding); + u32 columns_displayed = 0; + + if (ImGui::BeginTable("AsetBrowserContentTable", column_count)) + { + ImGui::TableNextRow(); + + for (const auto& entry : directory_entries) + { + if (std::filesystem::is_directory(entry)) + { + ImGui::TableNextColumn(); + ImGui::BeginGroup(); + if (ImGui::ImageButton(PathToString(entry).c_str(), + *m_state.m_editor_data.m_name_to_texture["folder"].get(), + thumbnail_imgui_size)) + { + m_current_path = entry; + } + ImGui::Text("%s", PathToString(entry.filename()).c_str()); + ImGui::EndGroup(); + columns_displayed++; + if (columns_displayed == column_count) + { + ImGui::TableNextRow(); + columns_displayed = 0; + } + } + } + + for (const auto& entry : directory_entries) + { + if (std::filesystem::is_regular_file(entry)) + { + const std::string filename = PathToString(entry.filename()); + if (!m_filter_text.empty() && filename.find(m_filter_text) == std::string::npos) + continue; + auto ext = entry.extension().string(); + Common::ToLower(&ext); + if (ext == ".dds" || ext == ".png") + { + ImGui::TableNextColumn(); + ImGui::BeginGroup(); + HandleAsset(entry, m_state.m_editor_data.m_name_to_texture["image"].get(), + "TextureAsset"); + ImGui::EndGroup(); + columns_displayed++; + } + else if (ext == ".rastershader") + { + ImGui::TableNextColumn(); + ImGui::BeginGroup(); + HandleAsset(entry, m_state.m_editor_data.m_name_to_texture["code"].get(), + "RasterShaderAsset"); + ImGui::EndGroup(); + columns_displayed++; + } + else if (ext == ".dolmesh") + { + ImGui::TableNextColumn(); + ImGui::BeginGroup(); + HandleAsset(entry, m_state.m_editor_data.m_name_to_texture["file"].get(), "MeshAsset"); + ImGui::EndGroup(); + columns_displayed++; + } + else if (ext == ".gltf") + { + ImGui::TableNextColumn(); + ImGui::BeginGroup(); + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.5); + ImGui::ImageButton(PathToString(entry).c_str(), + *m_state.m_editor_data.m_name_to_texture["file"].get(), + thumbnail_imgui_size); + ImGui::PopStyleVar(); + if (ImGui::BeginPopupContextItem()) + { + if (ImGui::Selectable("Import")) + { + m_is_mesh_import_active = true; + m_mesh_import_filename = WithUnifiedPathSeparators(PathToString(full_path / entry)); + } + + ImGui::EndPopup(); + } + ImGui::OpenPopupOnItemClick(PathToString(entry).c_str(), + ImGuiPopupFlags_MouseButtonRight); + + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.5); + ImGui::TextWrapped("%s", PathToString(entry.stem()).c_str()); + ImGui::PopStyleVar(); + + ImGui::EndGroup(); + columns_displayed++; + } + else if (ext == ".rastermaterial") + { + ImGui::TableNextColumn(); + ImGui::BeginGroup(); + HandleAsset(entry, m_state.m_editor_data.m_name_to_texture["file"].get(), + "RasterMaterialAsset"); + ImGui::EndGroup(); + columns_displayed++; + } + else if (ext == ".rendertarget") + { + ImGui::TableNextColumn(); + ImGui::BeginGroup(); + HandleAsset(entry, m_state.m_editor_data.m_name_to_texture["image"].get(), + "RenderTargetAsset"); + ImGui::EndGroup(); + columns_displayed++; + } + + if (columns_displayed == column_count) + { + ImGui::TableNextRow(); + columns_displayed = 0; + } + } + } + + if (m_is_mesh_import_active) + { + if (Controls::ShowMeshImportWindow(m_mesh_import_filename, &m_mesh_import_import_materials)) + { + m_is_mesh_import_active = false; + m_mesh_import_import_materials = false; + } + } + + if (m_is_generate_material_active) + { + if (Controls::ShowMaterialGenerateWindow(&m_material_generation_context)) + { + m_is_generate_material_active = false; + } + } + + if (ImGui::BeginPopupContextWindow("AssetBrowserGenericContextMenu", + ImGuiPopupFlags_NoOpenOverItems | + ImGuiPopupFlags_MouseButtonRight | + ImGuiPopupFlags_NoOpenOverExistingPopup)) + { + if (ImGui::BeginMenu("Create")) + { + if (ImGui::MenuItem("Material")) + { + const std::string name = FindNewName("New Material", ".rastermaterial", m_current_path); + const auto asset = GenerateMaterial( + name, m_current_path, m_state.m_editor_data.m_name_to_template["raster_material"], + m_state.m_user_data.m_asset_library.get()); + if (asset) + { + EditorEvents::ItemsSelectedEvent::Trigger(std::set{asset}); + GraphicsModEditor::EditorEvents::ChangeOccurredEvent::Trigger(); + BeginRename(asset->m_asset_id, PathToString(asset->m_asset_path.stem())); + } + } + if (ImGui::MenuItem("Shader")) + { + const auto metadata_name = FindNewName("New Shader", ".rastershader", m_current_path); + const std::string pixel_name = FindNewName("New Shader.ps", ".glsl", m_current_path); + const std::string vertex_name = FindNewName("New Shader.vs", ".glsl", m_current_path); + + const auto asset = + GenerateShader(m_current_path, pixel_name, vertex_name, metadata_name, + m_state.m_editor_data.m_name_to_template["raster_shader_ps"], + m_state.m_editor_data.m_name_to_template["raster_shader_vs"], + m_state.m_editor_data.m_name_to_template["raster_shader_meta"], + m_state.m_user_data.m_asset_library.get()); + if (asset) + { + EditorEvents::ItemsSelectedEvent::Trigger(std::set{asset}); + GraphicsModEditor::EditorEvents::ChangeOccurredEvent::Trigger(); + BeginRename(asset->m_asset_id, PathToString(asset->m_asset_path.stem())); + } + } + if (ImGui::MenuItem("Render Target")) + { + const std::string name = + FindNewName("New Render Target", ".rendertarget", m_current_path); + VideoCommon::RenderTargetData data; + data.m_texture_format = AbstractTextureFormat::RGBA8; + data.m_sampler = {}; + const auto asset = + GenerateRenderTarget(m_current_path, data, m_state.m_user_data.m_asset_library.get()); + if (asset) + { + EditorEvents::ItemsSelectedEvent::Trigger(std::set{asset}); + GraphicsModEditor::EditorEvents::ChangeOccurredEvent::Trigger(); + BeginRename(asset->m_asset_id, PathToString(asset->m_asset_path.stem())); + } + } + ImGui::EndMenu(); + } + if (ImGui::MenuItem("Generate Materials From Textures...")) + { + m_material_generation_context = {}; + m_material_generation_context.state = &m_state; + m_is_generate_material_active = true; + m_material_generation_context.input_path = PathToString(m_current_path); + m_material_generation_context.output_path = + PathToString(m_state.m_user_data.m_current_mod_path / "materials"); + } + ImGui::EndPopup(); + } + + const bool window_clicked = (!ImGui::IsAnyItemHovered() && + ImGui::IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows) && + ImGui::IsMouseClicked(ImGuiMouseButton_Left)); + if (ImGui::IsKeyPressed(ImGuiKey_Escape) || window_clicked) + { + EndRename(); + } + + ImGui::EndTable(); + } + + if (directory_entries.size() == 0) + { + ImGui::Text("No items in folder"); + } + + ImGui::End(); +} + +void AssetBrowserPanel::HandleAsset(const std::filesystem::path& asset_path, + AbstractTexture* icon_texture, std::string_view drag_drop_name) +{ + EditorAsset* asset = m_state.m_user_data.m_asset_library->GetAssetFromPath(asset_path); + if (asset) + { + AbstractTexture* texture = + m_state.m_user_data.m_asset_library->GetAssetPreview(asset->m_asset_id); + m_state.m_editor_data.m_assets_waiting_for_preview.erase(asset->m_asset_id); + if (!texture) + texture = icon_texture; + + if (ImGui::ImageButton(PathToString(asset_path).c_str(), *texture, thumbnail_imgui_size)) + { + EndRename(); + EditorEvents::ItemsSelectedEvent::Trigger(std::set{asset}); + } + if (ImGui::BeginPopupContextItem()) + { + EndRename(); + if (ImGui::MenuItem("Remove from project")) + { + m_state.m_user_data.m_asset_library->RemoveAsset(asset_path); + EditorEvents::ItemsSelectedEvent::Trigger(std::set{}); + GraphicsModEditor::EditorEvents::ChangeOccurredEvent::Trigger(); + } + if (ImGui::MenuItem("Rename")) + { + BeginRename(asset->m_asset_id, PathToString(asset_path.stem())); + } + ImGui::EndPopup(); + } + ImGui::OpenPopupOnItemClick(PathToString(asset_path).c_str(), ImGuiPopupFlags_MouseButtonRight); + if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) + { + ImGui::SetDragDropPayload(drag_drop_name.data(), asset->m_asset_id.c_str(), + asset->m_asset_id.size()); + + ImGui::Text("%s", PathToString(asset->m_asset_path.stem()).c_str()); + ImGui::EndDragDropSource(); + } + if (m_renamed_asset_id == asset->m_asset_id) + { + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, {0, 0}); + ImGui::PushStyleColor(ImGuiCol_FrameBg, 0); + if (!m_is_rename_focused) + { + ImGui::SetKeyboardFocusHere(); + m_is_rename_focused = true; + } + if (ImGui::InputText("##AssetRename", &m_rename_text, ImGuiInputTextFlags_EnterReturnsTrue)) + { + m_does_rename_have_error = false; + + const auto old_path = asset_path; + const auto new_path = + asset_path.parent_path() / (m_rename_text + PathToString(asset_path.extension())); + if (m_state.m_user_data.m_asset_library->RenameAsset(old_path, new_path)) + { + EndRename(); + GraphicsModEditor::EditorEvents::ChangeOccurredEvent::Trigger(); + } + else + { + if (!m_does_rename_have_error) + { + ImGui::OpenPopup("##RenameHasError"); + + // Focus seems to be stolen so tell input to focus again + m_is_rename_focused = false; + } + m_does_rename_have_error = true; + } + } + ImGui::PopStyleColor(); + ImGui::PopStyleVar(); + ImGui::PopStyleVar(); + + ImGui::SetNextWindowPos(ImVec2(ImGui::GetItemRectMax().x + 10, ImGui::GetItemRectMin().y)); + if (ImGui::BeginPopup("##RenameHasError", + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoResize | ImGuiWindowFlags_ChildWindow)) + { + const ImVec4 red_tint(1, 0, 0, 1); + ImGui::Image(*m_state.m_editor_data.m_name_to_texture["error"].get(), ImVec2(25, 25), + ImVec2(0, 0), ImVec2(1, 1), red_tint); + ImGui::SameLine(); + ImGui::Text("File already exists, choose a different name"); + if (!m_does_rename_have_error) + { + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + } + else + { + std::string asset_path_filename = PathToString(asset_path.stem()); + ImGui::TextWrapped("%s", asset_path_filename.c_str()); + if (ImGui::IsItemHovered()) + { + if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) + BeginRename(asset->m_asset_id, std::move(asset_path_filename)); + } + } + } + else + { + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.5); + if (ImGui::ImageButton(PathToString(asset_path).c_str(), *icon_texture, thumbnail_imgui_size)) + { + EndRename(); + } + ImGui::PopStyleVar(); + if (ImGui::BeginPopupContextItem()) + { + EndRename(); + if (ImGui::MenuItem("Add to project")) + { + m_state.m_user_data.m_asset_library->AddAsset(asset_path); + GraphicsModEditor::EditorEvents::ChangeOccurredEvent::Trigger(); + + asset = m_state.m_user_data.m_asset_library->GetAssetFromPath(asset_path); + if (asset) + { + auto& system = Core::System::GetInstance(); + auto& loader = system.GetCustomAssetLoader(); + // TODO: generate previews for other assets... + if (asset->m_asset_map.find("texture") != asset->m_asset_map.end()) + { + m_state.m_editor_data.m_assets_waiting_for_preview.try_emplace( + asset->m_asset_id, + loader.LoadGameTexture(asset->m_asset_id, m_state.m_user_data.m_asset_library)); + } + } + } + ImGui::EndPopup(); + } + ImGui::OpenPopupOnItemClick(PathToString(asset_path).c_str(), ImGuiPopupFlags_MouseButtonRight); + + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.5); + ImGui::TextWrapped("%s", PathToString(asset_path.stem()).c_str()); + ImGui::PopStyleVar(); + } +} + +void AssetBrowserPanel::BeginRename(const VideoCommon::CustomAssetLibrary::AssetID& asset_id, + std::string rename_text) +{ + m_renamed_asset_id = asset_id; + m_rename_text = std::move(rename_text); + m_is_rename_focused = false; +} + +void AssetBrowserPanel::EndRename() +{ + m_renamed_asset_id.reset(); + m_rename_text = ""; +} + +void AssetBrowserPanel::BrowseEvent(const VideoCommon::CustomAssetLibrary::AssetID& asset_id) +{ + EditorAsset* asset = m_state.m_user_data.m_asset_library->GetAssetFromID(asset_id); + if (asset) + { + m_current_path = asset->m_asset_path.parent_path(); + EditorEvents::ItemsSelectedEvent::Trigger(std::set{asset}); + } +} +} // namespace GraphicsModEditor::Panels diff --git a/Source/Core/VideoCommon/GraphicsModEditor/Panels/AssetBrowserPanel.h b/Source/Core/VideoCommon/GraphicsModEditor/Panels/AssetBrowserPanel.h new file mode 100644 index 0000000000..27b50dbd1e --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/Panels/AssetBrowserPanel.h @@ -0,0 +1,67 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#include + +#include "Common/HookableEvent.h" +#include "VideoCommon/Assets/CustomAssetLibrary.h" +#include "VideoCommon/GraphicsModEditor/EditorState.h" +#include "VideoCommon/GraphicsModEditor/MaterialGeneration.h" + +class AbstractTexture; + +namespace GraphicsModEditor::Panels +{ +class AssetBrowserPanel +{ +public: + explicit AssetBrowserPanel(EditorState& state); + + void ResetCurrentPath(); + + // Renders ImGui windows to the currently-bound framebuffer. + void DrawImGui(); + +private: + static constexpr u32 thumbnail_size = 150; + static constexpr u32 padding = 32; + static constexpr ImVec2 thumbnail_imgui_size{thumbnail_size, thumbnail_size}; + + void HandleAsset(const std::filesystem::path& asset_path, AbstractTexture* icon_texture, + std::string_view drag_drop_name); + void BeginRename(const VideoCommon::CustomAssetLibrary::AssetID& asset_id, + std::string rename_text); + void EndRename(); + void BrowseEvent(const VideoCommon::CustomAssetLibrary::AssetID& asset_id); + + Common::EventHook m_browse_event; + + EditorState& m_state; + + std::filesystem::path m_current_path; + + // Filter + std::string m_filter_text; + + // Rename + std::optional m_renamed_asset_id = std::nullopt; + std::string m_rename_text; + bool m_is_rename_focused = false; + bool m_does_rename_have_error = false; + + // Import mesh + bool m_is_mesh_import_active = false; + bool m_mesh_import_import_materials = false; + std::string m_mesh_import_filename; + + // Generate materials + bool m_is_generate_material_active = false; + RasterMaterialGenerationContext m_material_generation_context; +}; +} // namespace GraphicsModEditor::Panels diff --git a/Source/Core/VideoCommon/GraphicsModEditor/Panels/PropertiesPanel.cpp b/Source/Core/VideoCommon/GraphicsModEditor/Panels/PropertiesPanel.cpp new file mode 100644 index 0000000000..bcac8399df --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/Panels/PropertiesPanel.cpp @@ -0,0 +1,623 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModEditor/Panels/PropertiesPanel.h" + +#include + +#include +#include +#include + +#include "Common/EnumUtils.h" +#include "Common/VariantUtil.h" + +#include "VideoCommon/AbstractTexture.h" +#include "VideoCommon/GraphicsModEditor/Controls/MiscControls.h" +#include "VideoCommon/GraphicsModEditor/Controls/TagSelectionWindow.h" +#include "VideoCommon/GraphicsModEditor/EditorEvents.h" +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h" +#include "VideoCommon/Present.h" +#include "VideoCommon/TextureUtils.h" + +namespace GraphicsModEditor::Panels +{ +PropertiesPanel::PropertiesPanel(EditorState& state) + : m_state(state), m_material_control(m_state), m_mesh_control(m_state), + m_render_target_control(m_state), m_shader_control(m_state), m_texture_control(m_state) +{ + m_selection_event = EditorEvents::ItemsSelectedEvent::Register( + [this](const auto& selected_targets) { SelectionOccurred(selected_targets); }, + "EditorPropertiesPanel"); +} + +void PropertiesPanel::DrawImGui() +{ + const ImGuiViewport* main_viewport = ImGui::GetMainViewport(); + u32 default_window_height = g_presenter->GetTargetRectangle().GetHeight() - + ((float)g_presenter->GetTargetRectangle().GetHeight() * 0.1); + u32 default_window_width = ((float)g_presenter->GetTargetRectangle().GetWidth() * 0.15); + ImGui::SetNextWindowPos(ImVec2(main_viewport->WorkPos.x + + g_presenter->GetTargetRectangle().GetWidth() - + default_window_width * 1.25, + main_viewport->WorkPos.y + + ((float)g_presenter->GetTargetRectangle().GetHeight() * 0.05)), + ImGuiCond_FirstUseEver); + ImGui::SetNextWindowSize(ImVec2(default_window_width, default_window_height), + ImGuiCond_FirstUseEver); + + ImGui::Begin("Properties Panel"); + + if (m_selected_targets.size() > 1) + { + ImGui::Text("Multiple objects not yet supported"); + } + else if (m_selected_targets.size() == 1) + { + std::visit( + overloaded{[&](const GraphicsModSystem::DrawCallID& drawcallid) { + DrawCallIDSelected(drawcallid); + }, + [&](const GraphicsModSystem::TextureCacheID& tcache_id) { + TextureCacheIDSelected(tcache_id); + }, + [&](const GraphicsModSystem::LightID& light_id) { LightSelected(light_id); }, + [&](GraphicsModAction* action) { action->DrawImGui(); }, + [&](EditorAsset* asset_data) { AssetDataSelected(asset_data); }}, + *m_selected_targets.begin()); + } + ImGui::End(); +} + +void PropertiesPanel::DrawCallIDSelected(const GraphicsModSystem::DrawCallID& selected_object) +{ + const auto& data = m_state.m_runtime_data.m_draw_call_id_to_data[selected_object]; + + if (ImGui::BeginTable("DrawCallBasicForm", 2)) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("DisplayName"); + ImGui::TableNextColumn(); + + std::string friendly_name; + if (const auto user_iter = + m_state.m_user_data.m_draw_call_id_to_user_data.find(selected_object); + user_iter != m_state.m_user_data.m_draw_call_id_to_user_data.end()) + { + friendly_name = user_iter->second.m_friendly_name; + } + if (ImGui::InputText("##FrameTargetDisplayName", &friendly_name)) + { + auto& user_data = m_state.m_user_data.m_draw_call_id_to_user_data[selected_object]; + user_data.m_friendly_name = std::move(friendly_name); + GraphicsModEditor::EditorEvents::ChangeOccurredEvent::Trigger(); + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("ID"); + ImGui::TableNextColumn(); + ImGui::TextWrapped("%llu", Common::ToUnderlying(selected_object)); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Time Created"); + ImGui::TableNextColumn(); + ImGui::Text("%lld", static_cast(data.m_create_time.time_since_epoch().count())); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Cull Mode"); + ImGui::TableNextColumn(); + ImGui::Text("%s", fmt::to_string(data.draw_data.rasterization_state.cullmode).c_str()); + + ImGui::EndTable(); + } + + if (ImGui::CollapsingHeader("Camera", ImGuiTreeNodeFlags_DefaultOpen)) + { + if (ImGui::BeginTable("CameraForm", 2)) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Projection Type"); + ImGui::TableNextColumn(); + ImGui::Text("%s", fmt::to_string(data.draw_data.projection_type).c_str()); + + static constexpr double epsilon = 1e-5; + if (data.draw_data.projection_type == ProjectionType::Perspective) + { + if (data.draw_data.projection_mat[2] > epsilon) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("FOV"); + ImGui::TableNextColumn(); + + static constexpr double PI = 3.14159265358979323846; + const double fov = 2.0 * atan(1.0 / data.draw_data.projection_mat[2]) * 180.0 / PI; + ImGui::Text("%lf", fov); + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Aspect Ratio"); + ImGui::TableNextColumn(); + const double aspect_ratio = + data.draw_data.projection_mat[2] / data.draw_data.projection_mat[0]; + ImGui::Text("%lf", aspect_ratio); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Near"); + ImGui::TableNextColumn(); + const double near_plane = + data.draw_data.projection_mat[5] / (data.draw_data.projection_mat[4] - 1.0); + ImGui::Text("%lf", near_plane); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Far"); + ImGui::TableNextColumn(); + const double far_plane = + data.draw_data.projection_mat[5] / (data.draw_data.projection_mat[4] + 1.0); + ImGui::Text("%lf", far_plane); + } + + ImGui::EndTable(); + } + } + + if (ImGui::CollapsingHeader("Tags", ImGuiTreeNodeFlags_DefaultOpen)) + { + if (const auto user_data_iter = + m_state.m_user_data.m_draw_call_id_to_user_data.find(selected_object); + user_data_iter != m_state.m_user_data.m_draw_call_id_to_user_data.end()) + { + std::size_t tag_count = 0; + std::vector tag_names_to_remove; + for (const auto& tag : user_data_iter->second.m_tag_names) + { + if (const auto iter = m_state.m_editor_data.m_tags.find(tag); + iter != m_state.m_editor_data.m_tags.end()) + { + if (Controls::ColorButton(tag.c_str(), Controls::tag_size, + ImVec4(iter->second.m_color.x, iter->second.m_color.y, + iter->second.m_color.z, 1))) + { + tag_names_to_remove.push_back(tag); + } + tag_count++; + } + } + + for (const auto& tag_name : tag_names_to_remove) + { + std::erase(user_data_iter->second.m_tag_names, tag_name); + } + + if (!tag_names_to_remove.empty()) + { + GraphicsModEditor::EditorEvents::ChangeOccurredEvent::Trigger(); + } + + if (tag_count > tag_names_to_remove.size()) + { + ImGui::SameLine(); + } + } + + if (ImGui::SmallButton("+")) + { + m_tag_selection_window_active = true; + } + + if (m_tag_selection_window_active) + { + std::string new_tag; + if (Controls::TagSelectionWindow("TagSelectionWindow", &m_state, &new_tag)) + { + if (!new_tag.empty()) + { + auto& user_data = m_state.m_user_data.m_draw_call_id_to_user_data[selected_object]; + user_data.m_tag_names.push_back(std::move(new_tag)); + GraphicsModEditor::EditorEvents::ChangeOccurredEvent::Trigger(); + } + m_tag_selection_window_active = false; + } + } + } + + if (ImGui::CollapsingHeader("Geometry", ImGuiTreeNodeFlags_DefaultOpen)) + { + if (ImGui::BeginTable("DrawGeometryForm", 2)) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Index Count"); + ImGui::TableNextColumn(); + ImGui::Text("%zu", data.draw_data.index_count); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Vertex Count"); + ImGui::TableNextColumn(); + ImGui::Text("%zu", data.draw_data.vertex_count); + + ImGui::EndTable(); + } + } + + if (ImGui::CollapsingHeader("Blending", ImGuiTreeNodeFlags_DefaultOpen)) + { + if (ImGui::BeginTable("DrawBlendingForm", 2)) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Blend enabled?"); + ImGui::TableNextColumn(); + if (data.draw_data.blending_state.blendenable) + ImGui::Text("Yes"); + else + ImGui::Text("No"); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Logicop update enabled?"); + ImGui::TableNextColumn(); + if (data.draw_data.blending_state.logicopenable) + ImGui::Text("Yes"); + else + ImGui::Text("No"); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Color update enabled?"); + ImGui::TableNextColumn(); + if (data.draw_data.blending_state.colorupdate) + ImGui::Text("Yes"); + else + ImGui::Text("No"); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Alpha update enabled?"); + ImGui::TableNextColumn(); + if (data.draw_data.blending_state.alphaupdate) + ImGui::Text("Yes"); + else + ImGui::Text("No"); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Subtract set?"); + ImGui::TableNextColumn(); + if (data.draw_data.blending_state.subtract) + ImGui::Text("Yes"); + else + ImGui::Text("No"); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Subtract Alpha Set?"); + ImGui::TableNextColumn(); + if (data.draw_data.blending_state.subtractAlpha) + ImGui::Text("Yes"); + else + ImGui::Text("No"); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Use Dual Source?"); + ImGui::TableNextColumn(); + if (data.draw_data.blending_state.usedualsrc) + ImGui::Text("Yes"); + else + ImGui::Text("No"); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Destination factor"); + ImGui::TableNextColumn(); + ImGui::Text("%s", fmt::to_string(data.draw_data.blending_state.dstfactor).c_str()); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Destination alpha factor"); + ImGui::TableNextColumn(); + ImGui::Text("%s", fmt::to_string(data.draw_data.blending_state.dstfactoralpha).c_str()); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Source factor"); + ImGui::TableNextColumn(); + ImGui::Text("%s", fmt::to_string(data.draw_data.blending_state.srcfactor).c_str()); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Source factor"); + ImGui::TableNextColumn(); + ImGui::Text("%s", fmt::to_string(data.draw_data.blending_state.srcfactoralpha).c_str()); + + ImGui::EndTable(); + } + } + + if (ImGui::CollapsingHeader("Textures", ImGuiTreeNodeFlags_DefaultOpen)) + { + if (ImGui::BeginTable("DrawTexturesForm", 2)) + { + std::map hash_to_index; + for (std::size_t i = 0; i < data.draw_data.textures.size(); i++) + { + auto& texture = data.draw_data.textures[i]; + hash_to_index[texture.hash_name] = i; + } + + for (const auto& [hash_name, index] : hash_to_index) + { + auto& texture = data.draw_data.textures[index]; + const auto& texture_info = + m_state.m_runtime_data.m_texture_cache_id_to_data[texture.hash_name]; + const auto& texture_view = texture_info.texture; + if (texture_view.texture_data) + { + auto& sampler = data.draw_data.samplers[texture.unit]; + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + + ImGui::Text("Sampler (%i)", texture.unit); + + ImGui::TableNextColumn(); + + ImGuiTableFlags flags = ImGuiTableFlags_Borders; + if (ImGui::BeginTable("WrapModeTable", 2, flags)) + { + ImGui::TableSetupColumn("Direction"); + ImGui::TableSetupColumn("Wrap Mode"); + ImGui::TableHeadersRow(); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("u"); + ImGui::TableNextColumn(); + ImGui::Text("%s", fmt::to_string(sampler.tm0.wrap_u).c_str()); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("v"); + ImGui::TableNextColumn(); + ImGui::Text("%s", fmt::to_string(sampler.tm0.wrap_v).c_str()); + ImGui::EndTable(); + } + + if (ImGui::BeginTable("FilterModeTable", 2, flags)) + { + ImGui::TableSetupColumn("Type"); + ImGui::TableSetupColumn("Filter Mode"); + ImGui::TableHeadersRow(); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("min"); + ImGui::TableNextColumn(); + ImGui::Text("%s", fmt::to_string(sampler.tm0.min_filter).c_str()); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("mag"); + ImGui::TableNextColumn(); + ImGui::Text("%s", fmt::to_string(sampler.tm0.mag_filter).c_str()); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("mip"); + ImGui::TableNextColumn(); + ImGui::Text("%s", fmt::to_string(sampler.tm0.mipmap_filter).c_str()); + ImGui::EndTable(); + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Texture (%i)", texture.unit); + ImGui::TableNextColumn(); + + if (texture_info.m_active) + { + const float column_width = ImGui::GetContentRegionAvail().x; + float image_width = texture_view.texture_data->GetWidth(); + float image_height = texture_view.texture_data->GetHeight(); + const float image_aspect_ratio = image_width / image_height; + + const float final_width = std::min(image_width * 4, column_width); + image_width = final_width; + image_height = final_width / image_aspect_ratio; + + ImGui::ImageButton(texture_view.hash_name.data(), *texture_view.texture_data, + ImVec2{image_width, image_height}); + if (ImGui::BeginPopupContextItem()) + { + if (ImGui::Selectable("Dump")) + { + VideoCommon::TextureUtils::DumpTexture(*texture_view.texture_data, + texture.hash_name, 0, false); + } + if (ImGui::Selectable("Copy hash")) + { + ImGui::SetClipboardText(texture_view.hash_name.data()); + } + ImGui::EndPopup(); + } + ImGui::Text("%s %dx%d", texture.hash_name.data(), texture_view.texture_data->GetWidth(), + texture_view.texture_data->GetHeight()); + } + else + { + ImGui::Text( + "", texture.hash_name.data(), + static_cast(texture_info.m_create_time.time_since_epoch().count()), + static_cast( + texture_info.m_last_load_time.time_since_epoch().count())); + } + } + } + ImGui::EndTable(); + } + } +} + +void PropertiesPanel::TextureCacheIDSelected( + const GraphicsModSystem::TextureCacheID& selected_object) +{ + const auto& data = m_state.m_runtime_data.m_texture_cache_id_to_data[selected_object]; + auto& user_data = m_state.m_user_data.m_texture_cache_id_to_user_data[selected_object]; + + if (ImGui::BeginTable("TextureCacheTargetForm", 2)) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("DisplayName"); + ImGui::TableNextColumn(); + ImGui::InputText("##TextureCacheTargetDisplayName", &user_data.m_friendly_name); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("ID"); + ImGui::TableNextColumn(); + ImGui::Text("%s", data.m_id.c_str()); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Time Created"); + ImGui::TableNextColumn(); + ImGui::Text("%lld", static_cast(data.m_create_time.time_since_epoch().count())); + + if (data.texture.texture_data) + { + const float column_width = ImGui::GetContentRegionAvail().x; + float image_width = data.texture.texture_data->GetWidth(); + float image_height = data.texture.texture_data->GetHeight(); + const float image_aspect_ratio = image_width / image_height; + + image_width = column_width; + image_height = column_width * image_aspect_ratio; + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Texture"); + ImGui::TableNextColumn(); + ImGui::Image(*data.texture.texture_data, ImVec2{image_width, image_height}); + } + + ImGui::EndTable(); + } +} + +void PropertiesPanel::LightSelected(const GraphicsModSystem::LightID& selected_object) +{ + auto& data = m_state.m_runtime_data.m_light_id_to_data[selected_object]; + auto& user_data = m_state.m_user_data.m_light_id_to_user_data[selected_object]; + + if (ImGui::BeginTable("LightTargetForm", 2)) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("DisplayName"); + ImGui::TableNextColumn(); + ImGui::InputText("##LightTargetDisplayName", &user_data.m_friendly_name); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("ID"); + ImGui::TableNextColumn(); + ImGui::TextWrapped("%llu", Common::ToUnderlying(selected_object)); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Time Created"); + ImGui::TableNextColumn(); + ImGui::Text("%lld", static_cast(data.m_create_time.time_since_epoch().count())); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Color"); + ImGui::TableNextColumn(); + ImGui::InputInt4("##LightColor", data.m_color.data(), ImGuiInputTextFlags_ReadOnly); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Position"); + ImGui::TableNextColumn(); + ImGui::InputFloat4("##LightPosition", data.m_pos.data(), "%.3f", ImGuiInputTextFlags_ReadOnly); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Direction"); + ImGui::TableNextColumn(); + ImGui::InputFloat4("##LightDirection", data.m_dir.data(), "%.3f", ImGuiInputTextFlags_ReadOnly); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Distance Attenutation"); + ImGui::TableNextColumn(); + ImGui::InputFloat4("##LightDistAtt", data.m_distatt.data(), "%.3f", + ImGuiInputTextFlags_ReadOnly); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Cosine Attenutation"); + ImGui::TableNextColumn(); + ImGui::InputFloat4("##LightCosAtt", data.m_cosatt.data(), "%.3f", ImGuiInputTextFlags_ReadOnly); + + ImGui::EndTable(); + } +} + +void PropertiesPanel::AssetDataSelected(EditorAsset* selected_object) +{ + std::visit( + overloaded{ + [&](const std::unique_ptr& material_data) { + m_material_control.DrawImGui(selected_object->m_asset_id, material_data.get(), + &selected_object->m_last_data_write, + &selected_object->m_valid); + }, + [&](const std::unique_ptr& material_data) { + m_material_control.DrawImGui(selected_object->m_asset_id, material_data.get(), + &selected_object->m_valid); + }, + [&](const std::unique_ptr& pixel_shader_data) { + m_shader_control.DrawImGui(selected_object->m_asset_id, pixel_shader_data.get(), + &selected_object->m_last_data_write); + }, + [&](const std::unique_ptr& shader_data) { + m_shader_control.DrawImGui(selected_object->m_asset_id, shader_data.get()); + }, + [&](const std::unique_ptr& texture_data) { + auto asset_preview = + m_state.m_user_data.m_asset_library->GetAssetPreview(selected_object->m_asset_id); + m_texture_control.DrawImGui(selected_object->m_asset_id, texture_data.get(), + selected_object->m_asset_path, + &selected_object->m_last_data_write, asset_preview); + }, + [&](const std::unique_ptr& mesh_data) { + auto asset_preview = + m_state.m_user_data.m_asset_library->GetAssetPreview(selected_object->m_asset_id); + m_mesh_control.DrawImGui(selected_object->m_asset_id, mesh_data.get(), + selected_object->m_asset_path, + &selected_object->m_last_data_write, asset_preview); + }, + [&](const std::unique_ptr& render_target_data) { + auto asset_preview = + m_state.m_user_data.m_asset_library->GetAssetPreview(selected_object->m_asset_id); + m_render_target_control.DrawImGui(selected_object->m_asset_id, render_target_data.get(), + selected_object->m_asset_path, + &selected_object->m_last_data_write, asset_preview); + }}, + selected_object->m_data); +} + +void PropertiesPanel::SelectionOccurred(const std::set& selection) +{ + m_selected_targets = selection; +} +} // namespace GraphicsModEditor::Panels diff --git a/Source/Core/VideoCommon/GraphicsModEditor/Panels/PropertiesPanel.h b/Source/Core/VideoCommon/GraphicsModEditor/Panels/PropertiesPanel.h new file mode 100644 index 0000000000..2174d1005f --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/Panels/PropertiesPanel.h @@ -0,0 +1,49 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "Common/HookableEvent.h" +#include "VideoCommon/GraphicsModEditor/Controls/MaterialControl.h" +#include "VideoCommon/GraphicsModEditor/Controls/MeshControl.h" +#include "VideoCommon/GraphicsModEditor/Controls/RenderTargetControl.h" +#include "VideoCommon/GraphicsModEditor/Controls/ShaderControl.h" +#include "VideoCommon/GraphicsModEditor/Controls/TextureControl.h" +#include "VideoCommon/GraphicsModEditor/EditorState.h" +#include "VideoCommon/GraphicsModEditor/EditorTypes.h" +#include "VideoCommon/GraphicsModSystem/Types.h" + +namespace GraphicsModEditor::Panels +{ +class PropertiesPanel +{ +public: + PropertiesPanel(EditorState& state); + + // Renders ImGui windows to the currently-bound framebuffer. + void DrawImGui(); + +private: + void DrawCallIDSelected(const GraphicsModSystem::DrawCallID& selected_object); + void TextureCacheIDSelected(const GraphicsModSystem::TextureCacheID& selected_object); + void LightSelected(const GraphicsModSystem::LightID& selected_object); + void AssetDataSelected(EditorAsset* selected_object); + void SelectionOccurred(const std::set& selection); + + Common::EventHook m_selection_event; + + EditorState& m_state; + + std::set m_selected_targets; + + Controls::MaterialControl m_material_control; + Controls::MeshControl m_mesh_control; + Controls::RenderTargetControl m_render_target_control; + Controls::ShaderControl m_shader_control; + Controls::TextureControl m_texture_control; + + bool m_tag_selection_window_active = false; +}; +} // namespace GraphicsModEditor::Panels diff --git a/Source/Core/VideoCommon/GraphicsModEditor/RenderTargetGeneration.cpp b/Source/Core/VideoCommon/GraphicsModEditor/RenderTargetGeneration.cpp new file mode 100644 index 0000000000..68cdb80e6f --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/RenderTargetGeneration.cpp @@ -0,0 +1,29 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModEditor/RenderTargetGeneration.h" + +#include "Common/JsonUtil.h" +#include "Common/StringUtil.h" + +#include "VideoCommon/GraphicsModEditor/EditorState.h" + +namespace GraphicsModEditor +{ +EditorAsset* GenerateRenderTarget(const std::filesystem::path& output_path, + const VideoCommon::RenderTargetData& data, + GraphicsModEditor::EditorAssetSource* source) +{ + picojson::object obj; + VideoCommon::RenderTargetData::ToJson(&obj, data); + + const auto metadata_path = output_path / "metadata.rendertarget"; + if (!JsonToFile(PathToString(metadata_path), picojson::value{obj}, true)) + { + return nullptr; + } + + source->AddAsset(metadata_path); + return source->GetAssetFromPath(metadata_path); +} +} // namespace GraphicsModEditor diff --git a/Source/Core/VideoCommon/GraphicsModEditor/RenderTargetGeneration.h b/Source/Core/VideoCommon/GraphicsModEditor/RenderTargetGeneration.h new file mode 100644 index 0000000000..c82283ae54 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/RenderTargetGeneration.h @@ -0,0 +1,17 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "VideoCommon/Assets/RenderTargetAsset.h" + +namespace GraphicsModEditor +{ +struct EditorAsset; +class EditorAssetSource; +EditorAsset* GenerateRenderTarget(const std::filesystem::path& output_path, + const VideoCommon::RenderTargetData& data, + GraphicsModEditor::EditorAssetSource* source); +} // namespace GraphicsModEditor diff --git a/Source/Core/VideoCommon/GraphicsModEditor/SceneDumper.cpp b/Source/Core/VideoCommon/GraphicsModEditor/SceneDumper.cpp new file mode 100644 index 0000000000..1d0e032f90 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/SceneDumper.cpp @@ -0,0 +1,811 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModEditor/SceneDumper.h" + +#include +#include +#include + +#include +#include + +#include "Common/EnumUtils.h" +#include "Common/Logging/Log.h" + +#include "VideoCommon/AbstractTexture.h" +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModBackend.h" +#include "VideoCommon/OpcodeDecoding.h" +#include "VideoCommon/TextureUtils.h" +#include "VideoCommon/VertexManagerBase.h" +#include "VideoCommon/VideoEvents.h" + +namespace +{ +int ComponentFormatAndIntegerToComponentType(ComponentFormat format, bool integer) +{ + switch (format) + { + case ComponentFormat::Byte: + return TINYGLTF_COMPONENT_TYPE_BYTE; + case ComponentFormat::InvalidFloat5: + case ComponentFormat::InvalidFloat6: + case ComponentFormat::InvalidFloat7: + case ComponentFormat::Float: + { + if (integer) + { + return TINYGLTF_COMPONENT_TYPE_INT; + } + else + { + return TINYGLTF_COMPONENT_TYPE_FLOAT; + } + } + case ComponentFormat::Short: + return TINYGLTF_COMPONENT_TYPE_SHORT; + case ComponentFormat::UByte: + return TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE; + case ComponentFormat::UShort: + return TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT; + }; + + return -1; +} + +int ComponentCountToType(std::size_t count) +{ + if (count == 1) + return TINYGLTF_TYPE_SCALAR; + else if (count == 2) + return TINYGLTF_TYPE_VEC2; + else if (count == 3) + return TINYGLTF_TYPE_VEC3; + else if (count == 4) + return TINYGLTF_TYPE_VEC4; + return -1; +} + +int PrimitiveTypeToMode(PrimitiveType type) +{ + switch (type) + { + case PrimitiveType::Points: + return TINYGLTF_MODE_POINTS; + case PrimitiveType::Lines: + return TINYGLTF_MODE_LINE; + case PrimitiveType::Triangles: + return TINYGLTF_MODE_TRIANGLES; + case PrimitiveType::TriangleStrip: + return TINYGLTF_MODE_TRIANGLE_STRIP; + }; + + return -1; +} +} // namespace + +namespace GraphicsModEditor +{ +struct SceneDumper::SceneDumperImpl +{ + SceneDumperImpl() + { + m_model.asset.version = "2.0"; + m_model.asset.generator = "Dolphin emulator"; + } + + using NodeList = std::vector; + std::map> m_xfb_hash_to_scene; + NodeList m_current_node_list; + tinygltf::Model m_model; + + bool AreAllXFBsCapture(std::span xfbs_presented) + { + if (m_xfb_hash_to_scene.empty()) + return false; + + return std::all_of(xfbs_presented.begin(), xfbs_presented.end(), + [this](const std::string& xfb_presented) { + return m_xfb_hash_to_scene.contains(xfb_presented); + }); + } + + void SaveScenesToFile(const std::string& path, std::span xfbs_presented) + { + const bool embed_images = false; + const bool embed_buffers = false; + const bool pretty_print = true; + const bool write_binary = false; + + for (const auto& xfb_presented : xfbs_presented) + { + if (const auto it = m_xfb_hash_to_scene.find(xfb_presented); it != m_xfb_hash_to_scene.end()) + { + for (auto&& node : it->second) + { + m_model.nodes.push_back(std::move(node)); + } + } + } + + tinygltf::TinyGLTF gltf; + if (!gltf.WriteGltfSceneToFile(&m_model, path, embed_images, embed_buffers, pretty_print, + write_binary)) + { + ERROR_LOG_FMT(VIDEO, "Failed to write GLTF file to '{}'", path); + } + } +}; + +SceneDumper::SceneDumper() : m_index_buffer(VertexManagerBase::MAXIBUFFERSIZE) +{ + m_impl = std::make_unique(); + m_index_generator.Init(true); + m_index_generator.Start(m_index_buffer.data()); +} + +SceneDumper::~SceneDumper() = default; + +bool SceneDumper::IsDrawCallInRecording(GraphicsModSystem::DrawCallID draw_call_id) const +{ + return m_record_request.m_draw_call_ids.empty() || + m_record_request.m_draw_call_ids.contains(draw_call_id); +} + +void SceneDumper::AddDataToRecording(GraphicsModSystem::DrawCallID draw_call_id, + const GraphicsModSystem::DrawDataView& draw_data, + AdditionalDrawData additional_draw_data) +{ + const auto& declaration = draw_data.vertex_format->GetVertexDeclaration(); + + // Skip 2 point position elements for now + if (declaration.position.enable && declaration.position.components == 2) + return; + + // Initial primitive data + tinygltf::Primitive primitive; + if (draw_data.uid->rasterization_state.primitive == PrimitiveType::TriangleStrip) + { + // Triangle strips are used for primitive restart but we don't support that in GLTF + primitive.mode = PrimitiveTypeToMode(PrimitiveType::Triangles); + } + else + { + primitive.mode = PrimitiveTypeToMode(draw_data.uid->rasterization_state.primitive); + } + primitive.indices = static_cast(m_impl->m_model.accessors.size()); + + u32 available_texcoords = 0; + for (std::size_t i = 0; i < declaration.texcoords.size(); i++) + { + if (declaration.texcoords[i].enable && declaration.texcoords[i].components == 2) + { + available_texcoords++; + } + } + + // Material data + if (draw_data.textures.empty() || available_texcoords == 0 || + !m_record_request.m_include_materials) + { + const std::string default_material_name = "Dolphin_Default_Material"; + if (const auto iter = m_materialhash_to_material_id.find(default_material_name); + iter != m_materialhash_to_material_id.end()) + { + primitive.material = iter->second; + } + else + { + tinygltf::Material material; + material.pbrMetallicRoughness.baseColorFactor = {1.0f, 1.0f, 1.0f, 1.0f}; + material.doubleSided = true; + material.name = default_material_name; + + primitive.material = static_cast(m_impl->m_model.materials.size()); + m_impl->m_model.materials.push_back(material); + m_materialhash_to_material_id[default_material_name] = primitive.material; + } + } + else + { + std::string texture_material_name = ""; + for (const auto& texture : draw_data.textures) + { + texture_material_name += texture.hash_name; + } + if (const auto iter = m_materialhash_to_material_id.find(texture_material_name); + iter != m_materialhash_to_material_id.end()) + { + primitive.material = iter->second; + } + else + { + tinygltf::Material material; + material.pbrMetallicRoughness.baseColorFactor = {1.0f, 1.0f, 1.0f, 1.0f}; + material.doubleSided = true; + material.name = texture_material_name; + if (draw_data.uid->blending_state.blendenable && m_record_request.m_enable_blending) + { + material.alphaMode = "BLEND"; + } + for (const auto& texture : draw_data.textures) + { + if (material.pbrMetallicRoughness.baseColorTexture.index != -1) + { + break; + } + + if (const auto texture_iter = m_texturehash_to_texture_id.find(texture.hash_name); + texture_iter != m_texturehash_to_texture_id.end()) + { + material.pbrMetallicRoughness.baseColorTexture.index = texture_iter->second; + } + else + { + VideoCommon::TextureUtils::DumpTexture(*texture.texture_data, + std::string{texture.hash_name}, 0, false); + + tinygltf::Texture tinygltf_texture; + tinygltf_texture.source = static_cast(m_impl->m_model.images.size()); + + tinygltf::Image image; + image.uri = fmt::format("{}.png", texture.hash_name); + m_impl->m_model.images.push_back(image); + + material.pbrMetallicRoughness.baseColorTexture.index = + static_cast(m_impl->m_model.textures.size()); + m_impl->m_model.textures.push_back(tinygltf_texture); + + m_texturehash_to_texture_id[std::string{texture.hash_name}] = + material.pbrMetallicRoughness.baseColorTexture.index; + } + } + primitive.material = static_cast(m_impl->m_model.materials.size()); + m_materialhash_to_material_id[texture_material_name] = primitive.material; + m_impl->m_model.materials.push_back(material); + } + } + + // Index data + tinygltf::Accessor index_accessor; + index_accessor.name = fmt::format("Accessor 'INDEX' for {}", Common::ToUnderlying(draw_call_id)); + index_accessor.bufferView = static_cast(m_impl->m_model.bufferViews.size()); + index_accessor.componentType = TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT; + index_accessor.byteOffset = 0; + index_accessor.type = TINYGLTF_TYPE_SCALAR; + index_accessor.count = m_index_generator.GetIndexLen(); + + auto indice_ptr = reinterpret_cast(m_index_generator.GetIndexDataStart()); + for (std::size_t indice_index = 0; indice_index < m_index_generator.GetIndexLen(); indice_index++) + { + u16 index = 0; + std::memcpy(&index, indice_ptr + index_accessor.byteOffset, sizeof(u16)); + + if (index_accessor.minValues.empty()) + { + index_accessor.minValues.push_back(static_cast(index)); + } + else + { + u16 last_min = static_cast(index_accessor.minValues[0]); + + if (index < last_min) + index_accessor.minValues[0] = index; + } + + if (index_accessor.maxValues.empty()) + { + index_accessor.maxValues.push_back(static_cast(index)); + } + else + { + u16 last_max = static_cast(index_accessor.maxValues[0]); + + if (index > last_max) + index_accessor.maxValues[0] = index; + } + indice_ptr += sizeof(u16); + } + + m_impl->m_model.accessors.push_back(std::move(index_accessor)); + + tinygltf::BufferView index_buffer_view; + index_buffer_view.buffer = static_cast(m_impl->m_model.buffers.size()); + index_buffer_view.byteLength = m_index_generator.GetIndexLen() * sizeof(u16); + index_buffer_view.byteOffset = 0; + index_buffer_view.target = TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER; + m_impl->m_model.bufferViews.push_back(std::move(index_buffer_view)); + + tinygltf::Buffer index_buffer; + + const auto index_data = + reinterpret_cast(m_index_generator.GetIndexDataStart()); + index_buffer.data = {index_data, index_data + index_buffer_view.byteLength}; + m_impl->m_model.buffers.push_back(std::move(index_buffer)); + + // Fill out all vertex data + const std::size_t stride = declaration.stride; + + Common::Vec3 origin_skinned; + if (declaration.position.enable) + { + primitive.attributes["POSITION"] = static_cast(m_impl->m_model.accessors.size()); + + tinygltf::Accessor accessor; + accessor.name = fmt::format("Accessor 'POSITION' for {}", Common::ToUnderlying(draw_call_id)); + accessor.bufferView = static_cast(m_impl->m_model.bufferViews.size()); + accessor.componentType = ComponentFormatAndIntegerToComponentType(declaration.position.type, + declaration.position.integer); + accessor.byteOffset = declaration.position.offset; + accessor.type = ComponentCountToType(declaration.position.components); + accessor.count = draw_data.vertex_data.size(); + + auto vert_ptr = reinterpret_cast(draw_data.vertex_data.data()); + + // If gpu skinning is turned on but transforms is turned off, we need to calculate + // what the positions are when centered at the origin + std::optional min_position_skinned; + std::optional max_position_skinned; + if (m_record_request.m_apply_gpu_skinning && !m_record_request.m_include_transform && + !draw_data.gpu_skinning_position_transform.empty() && declaration.posmtx.enable) + { + for (std::size_t vert_index = 0; vert_index < draw_data.vertex_data.size(); vert_index++) + { + Common::Vec3 vertex_position; + std::memcpy(&vertex_position.x, vert_ptr + accessor.byteOffset, sizeof(float)); + std::memcpy(&vertex_position.y, vert_ptr + sizeof(float) + accessor.byteOffset, + sizeof(float)); + std::memcpy(&vertex_position.z, vert_ptr + sizeof(float) * 2 + accessor.byteOffset, + sizeof(float)); + + u32 gpu_skin_index; + std::memcpy(&gpu_skin_index, vert_ptr + declaration.posmtx.offset, sizeof(u32)); + + Common::Matrix44 position_transform; + for (std::size_t i = 0; i < 3; i++) + { + position_transform.data[i * 4 + 0] = + draw_data.gpu_skinning_position_transform[gpu_skin_index + i][0]; + position_transform.data[i * 4 + 1] = + draw_data.gpu_skinning_position_transform[gpu_skin_index + i][1]; + position_transform.data[i * 4 + 2] = + draw_data.gpu_skinning_position_transform[gpu_skin_index + i][2]; + position_transform.data[i * 4 + 3] = + draw_data.gpu_skinning_position_transform[gpu_skin_index + i][3]; + } + position_transform.data[12] = 0; + position_transform.data[13] = 0; + position_transform.data[14] = 0; + position_transform.data[15] = 1; + + // Apply the transform to the position + Common::Vec4 vp(vertex_position, 1); + vp = position_transform * vp; + vertex_position.x = vp.x; + vertex_position.y = vp.y; + vertex_position.z = vp.z; + + if (!min_position_skinned) + { + min_position_skinned = vertex_position; + } + else + { + if (vertex_position.x < min_position_skinned->x) + min_position_skinned->x = vertex_position.x; + if (vertex_position.y < min_position_skinned->y) + min_position_skinned->y = vertex_position.y; + if (vertex_position.z < min_position_skinned->z) + min_position_skinned->z = vertex_position.z; + } + + if (!max_position_skinned) + { + max_position_skinned = vertex_position; + } + else + { + if (vertex_position.x > max_position_skinned->x) + max_position_skinned->x = vertex_position.x; + if (vertex_position.y > max_position_skinned->y) + max_position_skinned->y = vertex_position.y; + if (vertex_position.z > max_position_skinned->z) + max_position_skinned->z = vertex_position.z; + } + vert_ptr += stride; + } + } + if (min_position_skinned && max_position_skinned) + { + origin_skinned = (*max_position_skinned - *min_position_skinned) / 2.0f; + } + else if (min_position_skinned) + { + origin_skinned = -*min_position_skinned / 2.0f; + } + else if (max_position_skinned) + { + origin_skinned = *max_position_skinned / 2.0f; + } + + vert_ptr = reinterpret_cast(draw_data.vertex_data.data()); + for (std::size_t vert_index = 0; vert_index < draw_data.vertex_data.size(); vert_index++) + { + Common::Vec3 vertex_position; + std::memcpy(&vertex_position.x, vert_ptr + accessor.byteOffset, sizeof(float)); + std::memcpy(&vertex_position.y, vert_ptr + sizeof(float) + accessor.byteOffset, + sizeof(float)); + std::memcpy(&vertex_position.z, vert_ptr + sizeof(float) * 2 + accessor.byteOffset, + sizeof(float)); + + if (m_record_request.m_apply_gpu_skinning && + !draw_data.gpu_skinning_position_transform.empty() && declaration.posmtx.enable) + { + u32 gpu_skin_index; + std::memcpy(&gpu_skin_index, vert_ptr + declaration.posmtx.offset, sizeof(u32)); + + Common::Matrix44 position_transform; + for (std::size_t i = 0; i < 3; i++) + { + position_transform.data[i * 4 + 0] = + draw_data.gpu_skinning_position_transform[gpu_skin_index + i][0]; + position_transform.data[i * 4 + 1] = + draw_data.gpu_skinning_position_transform[gpu_skin_index + i][1]; + position_transform.data[i * 4 + 2] = + draw_data.gpu_skinning_position_transform[gpu_skin_index + i][2]; + position_transform.data[i * 4 + 3] = + draw_data.gpu_skinning_position_transform[gpu_skin_index + i][3]; + } + position_transform.data[12] = 0; + position_transform.data[13] = 0; + position_transform.data[14] = 0; + position_transform.data[15] = 1; + + // Apply the transform to the position + Common::Vec4 vp(vertex_position, 1); + vp = position_transform * vp; + vertex_position.x = vp.x; + vertex_position.y = vp.y; + vertex_position.z = vp.z; + + if (!m_record_request.m_include_transform) + { + vertex_position -= origin_skinned; + } + } + + if (accessor.minValues.empty()) + { + accessor.minValues.push_back(static_cast(vertex_position.x)); + accessor.minValues.push_back(static_cast(vertex_position.y)); + accessor.minValues.push_back(static_cast(vertex_position.z)); + } + else + { + const float last_min_x = static_cast(accessor.minValues[0]); + const float last_min_y = static_cast(accessor.minValues[1]); + const float last_min_z = static_cast(accessor.minValues[2]); + + if (vertex_position.x < last_min_x) + accessor.minValues[0] = vertex_position.x; + if (vertex_position.y < last_min_y) + accessor.minValues[1] = vertex_position.y; + if (vertex_position.z < last_min_z) + accessor.minValues[2] = vertex_position.z; + } + + if (accessor.maxValues.empty()) + { + accessor.maxValues.push_back(static_cast(vertex_position.x)); + accessor.maxValues.push_back(static_cast(vertex_position.y)); + accessor.maxValues.push_back(static_cast(vertex_position.z)); + } + else + { + const float last_max_x = static_cast(accessor.maxValues[0]); + const float last_max_y = static_cast(accessor.maxValues[1]); + const float last_max_z = static_cast(accessor.maxValues[2]); + + if (vertex_position.x > last_max_x) + accessor.maxValues[0] = vertex_position.x; + if (vertex_position.y > last_max_y) + accessor.maxValues[1] = vertex_position.y; + if (vertex_position.z > last_max_z) + accessor.maxValues[2] = vertex_position.z; + } + vert_ptr += stride; + } + + m_impl->m_model.accessors.push_back(std::move(accessor)); + } + + static std::array color_names{"COLOR_0", "COLOR_1"}; + for (std::size_t i = 0; i < declaration.colors.size(); i++) + { + if (declaration.colors[i].enable) + { + primitive.attributes[color_names[i]] = static_cast(m_impl->m_model.accessors.size()); + + tinygltf::Accessor accessor; + accessor.name = + fmt::format("Accessor '{}' for {}", color_names[i], Common::ToUnderlying(draw_call_id)); + accessor.bufferView = static_cast(m_impl->m_model.bufferViews.size()); + accessor.componentType = ComponentFormatAndIntegerToComponentType( + declaration.colors[i].type, declaration.colors[i].integer); + accessor.byteOffset = declaration.colors[i].offset; + accessor.type = ComponentCountToType(declaration.colors[i].components); + accessor.count = draw_data.vertex_data.size(); + accessor.normalized = declaration.colors[i].type == ComponentFormat::UByte || + declaration.colors[i].type == ComponentFormat::UShort || + declaration.colors[i].type == ComponentFormat::Byte || + declaration.colors[i].type == ComponentFormat::Short; + + m_impl->m_model.accessors.push_back(std::move(accessor)); + } + } + + // TODO: tangent/binormal? + static std::array norm_names{"NORMAL"}; + for (std::size_t i = 0; i < declaration.normals.size(); i++) + { + if (declaration.normals[i].enable) + { + primitive.attributes[norm_names[i]] = static_cast(m_impl->m_model.accessors.size()); + + tinygltf::Accessor accessor; + accessor.name = + fmt::format("Accessor '{}' for {}", norm_names[i], Common::ToUnderlying(draw_call_id)); + accessor.bufferView = static_cast(m_impl->m_model.bufferViews.size()); + accessor.componentType = ComponentFormatAndIntegerToComponentType( + declaration.normals[i].type, declaration.normals[i].integer); + accessor.byteOffset = declaration.normals[i].offset; + accessor.type = ComponentCountToType(declaration.normals[i].components); + accessor.count = draw_data.vertex_data.size(); + + m_impl->m_model.accessors.push_back(std::move(accessor)); + } + } + + static std::array texcoord_names = { + "TEXCOORD_0", "TEXCOORD_1", "TEXCOORD_2", "TEXCOORD_3", + "TEXCOORD_4", "TEXCOORD_5", "TEXCOORD_6", "TEXCOORD_7", + }; + for (std::size_t i = 0; i < declaration.texcoords.size(); i++) + { + // Ignore 3 component texcoords for now..they are tex matrixes? + if (declaration.texcoords[i].enable && declaration.texcoords[i].components == 2) + { + primitive.attributes[texcoord_names[i]] = static_cast(m_impl->m_model.accessors.size()); + + tinygltf::Accessor accessor; + accessor.name = fmt::format("Accessor '{}' for {}", texcoord_names[i], + Common::ToUnderlying(draw_call_id)); + accessor.bufferView = static_cast(m_impl->m_model.bufferViews.size()); + accessor.componentType = ComponentFormatAndIntegerToComponentType( + declaration.texcoords[i].type, declaration.texcoords[i].integer); + accessor.byteOffset = declaration.texcoords[i].offset; + accessor.type = ComponentCountToType(declaration.texcoords[i].components); + accessor.count = draw_data.vertex_data.size(); + accessor.normalized = declaration.texcoords[i].type == ComponentFormat::UByte || + declaration.texcoords[i].type == ComponentFormat::UShort || + declaration.texcoords[i].type == ComponentFormat::Byte || + declaration.texcoords[i].type == ComponentFormat::Short; + + m_impl->m_model.accessors.push_back(std::move(accessor)); + } + } + + // Vertex buffer data + tinygltf::BufferView vertex_buffer_view; + vertex_buffer_view.buffer = static_cast(m_impl->m_model.buffers.size()); + vertex_buffer_view.byteLength = draw_data.vertex_data.size() * stride; + vertex_buffer_view.byteOffset = 0; + vertex_buffer_view.byteStride = stride; + vertex_buffer_view.target = TINYGLTF_TARGET_ARRAY_BUFFER; + m_impl->m_model.bufferViews.push_back(std::move(vertex_buffer_view)); + + tinygltf::Buffer buffer; + const auto vert_data = static_cast(draw_data.vertex_data.data()); + buffer.data = {vert_data, vert_data + vertex_buffer_view.byteLength}; + + // If gpu skinning is turned on but transforms is turned off, we need to calculate + // what the positions are when centered at the origin + if (m_record_request.m_apply_gpu_skinning && !draw_data.gpu_skinning_position_transform.empty() && + declaration.posmtx.enable && declaration.position.enable) + { + auto vert_ptr = buffer.data.data(); + for (std::size_t vert_index = 0; vert_index < draw_data.vertex_data.size(); vert_index++) + { + Common::Vec3 vertex_position; + std::memcpy(&vertex_position.x, vert_ptr + declaration.position.offset, sizeof(float)); + std::memcpy(&vertex_position.y, vert_ptr + sizeof(float) + declaration.position.offset, + sizeof(float)); + std::memcpy(&vertex_position.z, vert_ptr + sizeof(float) * 2 + declaration.position.offset, + sizeof(float)); + + u32 gpu_skin_index; + std::memcpy(&gpu_skin_index, vert_ptr + declaration.posmtx.offset, sizeof(u32)); + + Common::Matrix44 position_transform; + for (std::size_t i = 0; i < 3; i++) + { + position_transform.data[i * 4 + 0] = + draw_data.gpu_skinning_position_transform[gpu_skin_index + i][0]; + position_transform.data[i * 4 + 1] = + draw_data.gpu_skinning_position_transform[gpu_skin_index + i][1]; + position_transform.data[i * 4 + 2] = + draw_data.gpu_skinning_position_transform[gpu_skin_index + i][2]; + position_transform.data[i * 4 + 3] = + draw_data.gpu_skinning_position_transform[gpu_skin_index + i][3]; + } + position_transform.data[12] = 0; + position_transform.data[13] = 0; + position_transform.data[14] = 0; + position_transform.data[15] = 1; + + // Apply the transform to the position + Common::Vec4 vp(vertex_position, 1); + vp = position_transform * vp; + vertex_position.x = vp.x; + vertex_position.y = vp.y; + vertex_position.z = vp.z; + + if (!m_record_request.m_include_transform) + { + vertex_position -= origin_skinned; + } + + std::memcpy(vert_ptr + declaration.position.offset, &vertex_position.x, sizeof(float)); + std::memcpy(vert_ptr + sizeof(float) + declaration.position.offset, &vertex_position.y, + sizeof(float)); + std::memcpy(vert_ptr + sizeof(float) * 2 + declaration.position.offset, &vertex_position.z, + sizeof(float)); + + if (declaration.normals[0].enable) + { + Common::Vec3 vertex_normal; + std::memcpy(&vertex_normal.x, vert_ptr + declaration.normals[0].offset, sizeof(float)); + std::memcpy(&vertex_normal.y, vert_ptr + sizeof(float) + declaration.normals[0].offset, + sizeof(float)); + std::memcpy(&vertex_normal.z, vert_ptr + sizeof(float) * 2 + declaration.normals[0].offset, + sizeof(float)); + + Common::Matrix33 normal_transform; + for (std::size_t i = 0; i < 3; i++) + { + normal_transform.data[i * 3 + 0] = + draw_data.gpu_skinning_normal_transform[gpu_skin_index + i][0]; + normal_transform.data[i * 3 + 1] = + draw_data.gpu_skinning_normal_transform[gpu_skin_index + i][1]; + normal_transform.data[i * 3 + 2] = + draw_data.gpu_skinning_normal_transform[gpu_skin_index + i][2]; + } + + // Apply the transform to the normal + vertex_normal = normal_transform * vertex_normal; + + // Save into output + std::memcpy(vert_ptr + declaration.normals[0].offset, &vertex_normal.x, sizeof(float)); + std::memcpy(vert_ptr + sizeof(float) + declaration.normals[0].offset, &vertex_normal.y, + sizeof(float)); + std::memcpy(vert_ptr + sizeof(float) * 2 + declaration.normals[0].offset, &vertex_normal.z, + sizeof(float)); + } + + vert_ptr += stride; + } + } + + m_impl->m_model.buffers.push_back(std::move(buffer)); + + // Node data + tinygltf::Node node; + node.name = fmt::format("Node {} for xfb {}", Common::ToUnderlying(draw_call_id), + m_xfbs_since_recording_present); + node.mesh = static_cast(m_impl->m_model.meshes.size()); + + // We expect to get data as a 3x3 if there's a global transform + if (m_record_request.m_include_transform && additional_draw_data.transform.size() == 12) + { + // GLTF expects to be passed a 4x4 + node.matrix.reserve(16); + + for (int w = 0; w < 4; w++) + { + for (int h = 0; h < 3; h++) + { + node.matrix.push_back(additional_draw_data.transform[w + h * 4]); + } + if (w == 3) + { + node.matrix.push_back(1); + } + else + { + node.matrix.push_back(0); + } + } + } + m_impl->m_current_node_list.push_back(std::move(node)); + + // Mesh data + tinygltf::Mesh mesh; + mesh.name = fmt::format("Mesh {}", Common::ToUnderlying(draw_call_id)); + mesh.primitives.push_back(std::move(primitive)); + m_impl->m_model.meshes.push_back(std::move(mesh)); +} + +void SceneDumper::Record(const std::string& path, const RecordingRequest& request) +{ + m_scene_save_path = path; + m_record_request = request; + m_recording_state = RecordingState::WANTS_RECORDING; +} + +bool SceneDumper::IsRecording() const +{ + return m_recording_state == RecordingState::IS_RECORDING; +} + +void SceneDumper::OnXFBCreated(const std::string& hash) +{ + if (m_recording_state != RecordingState::IS_RECORDING) + { + return; + } + + m_xfbs_since_recording_present++; + + // We saw a XFB create without any data and we just started capturing + // ignore it + if (m_impl->m_current_node_list.empty() && m_xfbs_since_recording_present == 1) + { + return; + } + + // When our frame present happens, we might be in the middle of an xfb + // which means this initial create won't have all the data that we expect + // it to have. Skip the first xfb and start collecting data after that + // This ensures our capture has all data it needs + if (m_xfbs_since_recording_present > 1) + { + m_impl->m_xfb_hash_to_scene.try_emplace(hash, std::move(m_impl->m_current_node_list)); + } + m_impl->m_current_node_list = {}; +} + +void SceneDumper::OnFramePresented(std::span xfbs_presented) +{ + if (m_recording_state == RecordingState::IS_RECORDING) + { + // If all our xfbs aren't captured in this frame, wait for next frame + if (!m_impl->AreAllXFBsCapture(xfbs_presented)) + return; + + m_impl->SaveScenesToFile(m_scene_save_path, xfbs_presented); + m_recording_state = RecordingState::NOT_RECORDING; + + // Release state + m_record_request = {}; + m_materialhash_to_material_id = {}; + m_texturehash_to_texture_id = {}; + m_impl = std::make_unique(); + m_xfbs_since_recording_present = 0; + } + + if (m_recording_state == RecordingState::WANTS_RECORDING) + { + m_recording_state = RecordingState::IS_RECORDING; + } +} + +void SceneDumper::AddIndices(OpcodeDecoder::Primitive primitive, u32 num_vertices) +{ + m_index_generator.AddIndices(primitive, num_vertices); +} + +void SceneDumper::ResetIndices() +{ + m_index_generator.Start(m_index_buffer.data()); +} +} // namespace GraphicsModEditor diff --git a/Source/Core/VideoCommon/GraphicsModEditor/SceneDumper.h b/Source/Core/VideoCommon/GraphicsModEditor/SceneDumper.h new file mode 100644 index 0000000000..1194867af8 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/SceneDumper.h @@ -0,0 +1,88 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "Common/CommonTypes.h" +#include "Common/SmallVector.h" + +#include "VideoCommon/GraphicsModSystem/Types.h" +#include "VideoCommon/IndexGenerator.h" +#include "VideoCommon/NativeVertexFormat.h" +#include "VideoCommon/OpcodeDecoding.h" +#include "VideoCommon/RenderState.h" + +class AbstractTexture; + +namespace GraphicsModSystem +{ +struct DrawData; +} + +namespace GraphicsModEditor +{ +class SceneDumper +{ +public: + SceneDumper(); + ~SceneDumper(); + + struct AdditionalDrawData + { + std::span transform; + }; + bool IsDrawCallInRecording(GraphicsModSystem::DrawCallID draw_call_id) const; + void AddDataToRecording(GraphicsModSystem::DrawCallID draw_call_id, + const GraphicsModSystem::DrawDataView& draw_data, + AdditionalDrawData additional_draw_data); + + struct RecordingRequest + { + std::unordered_set m_draw_call_ids; + + bool m_enable_blending = false; + bool m_apply_gpu_skinning = true; + bool m_include_transform = true; + bool m_include_materials = true; + bool m_ignore_orthographic = false; + }; + void Record(const std::string& path, const RecordingRequest& request); + bool IsRecording() const; + + void OnXFBCreated(const std::string& hash); + void OnFramePresented(std::span xfbs_presented); + + void AddIndices(OpcodeDecoder::Primitive primitive, u32 num_vertices); + void ResetIndices(); + +private: + enum RecordingState + { + NOT_RECORDING, + WANTS_RECORDING, + IS_RECORDING + }; + RecordingState m_recording_state = RecordingState::NOT_RECORDING; + + std::map> m_materialhash_to_material_id; + std::map> m_texturehash_to_texture_id; + RecordingRequest m_record_request; + std::string m_scene_save_path; + + u8 m_xfbs_since_recording_present = 0; + + struct SceneDumperImpl; + std::unique_ptr m_impl; + + std::vector m_index_buffer; + IndexGenerator m_index_generator; +}; +} // namespace GraphicsModEditor diff --git a/Source/Core/VideoCommon/GraphicsModEditor/SceneUtils.cpp b/Source/Core/VideoCommon/GraphicsModEditor/SceneUtils.cpp new file mode 100644 index 0000000000..050848c6fa --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/SceneUtils.cpp @@ -0,0 +1,35 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModEditor/SceneUtils.h" + +#include + +#include "Common/EnumUtils.h" + +namespace GraphicsModEditor +{ +std::string GetDrawCallName(const EditorState& editor_state, + GraphicsModSystem::DrawCallID draw_call) +{ + if (draw_call == GraphicsModSystem::DrawCallID::INVALID) + return ""; + + std::string draw_call_name = std::to_string(Common::ToUnderlying(draw_call)); + auto& draw_call_to_user_data = editor_state.m_user_data.m_draw_call_id_to_user_data; + if (const auto user_data_iter = draw_call_to_user_data.find(draw_call); + user_data_iter != draw_call_to_user_data.end()) + { + if (!user_data_iter->second.m_friendly_name.empty()) + draw_call_name = user_data_iter->second.m_friendly_name; + } + return draw_call_name; +} + +std::string GetActionName(GraphicsModAction* action) +{ + if (!action) [[unlikely]] + return ""; + return fmt::format("{}-{}", action->GetFactoryName(), action->GetID()); +} +} // namespace GraphicsModEditor diff --git a/Source/Core/VideoCommon/GraphicsModEditor/SceneUtils.h b/Source/Core/VideoCommon/GraphicsModEditor/SceneUtils.h new file mode 100644 index 0000000000..ac19f2bf1f --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/SceneUtils.h @@ -0,0 +1,36 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#include "VideoCommon/GraphicsModEditor/EditorState.h" +#include "VideoCommon/GraphicsModSystem/Types.h" + +namespace GraphicsModEditor +{ +std::string GetDrawCallName(const EditorState& editor_state, + GraphicsModSystem::DrawCallID draw_call); +std::string GetActionName(GraphicsModAction* action); + +template ActionType> +std::vector GetActionsForDrawCall(const EditorState& editor_state, + GraphicsModSystem::DrawCallID draw_call) +{ + std::vector result; + auto& draw_call_id_to_actions = editor_state.m_user_data.m_draw_call_id_to_actions; + if (const auto actions_iter = draw_call_id_to_actions.find(draw_call); + actions_iter != draw_call_id_to_actions.end()) + { + for (GraphicsModAction* action : actions_iter->second) + { + if (action->GetFactoryName() == ActionType::factory_name) + result.push_back(static_cast(action)); + } + } + return result; +} +} // namespace GraphicsModEditor diff --git a/Source/Core/VideoCommon/GraphicsModEditor/ShaderGeneration.cpp b/Source/Core/VideoCommon/GraphicsModEditor/ShaderGeneration.cpp new file mode 100644 index 0000000000..c81b30ebb8 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/ShaderGeneration.cpp @@ -0,0 +1,53 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModEditor/ShaderGeneration.h" + +#include "Common/FileUtil.h" +#include "Common/StringUtil.h" + +#include "VideoCommon/GraphicsModEditor/EditorState.h" + +namespace GraphicsModEditor +{ +EditorAsset* GenerateShader(const std::filesystem::path& output_path, + const ShaderGenerationContext& context, + GraphicsModEditor::EditorAssetSource* source) +{ + picojson::object obj; + VideoCommon::RasterShaderData::ToJson(obj, context.shader_template); + + return GenerateShader(output_path, context.pixel_name, context.vertex_name, context.metadata_name, + context.shader_template.m_pixel_source, + context.shader_template.m_vertex_source, picojson::value{obj}.serialize(), + source); +} + +EditorAsset* GenerateShader(const std::filesystem::path& output_path, std::string_view pixel_name, + std::string_view vertex_name, std::string_view metadata_name, + std::string_view pixel_source, std::string_view vertex_source, + std::string_view metadata_source, + GraphicsModEditor::EditorAssetSource* source) +{ + const auto metadata_path = output_path / metadata_name; + if (!File::WriteStringToFile(PathToString(metadata_path), metadata_source)) + { + return nullptr; + } + + const auto vertex_path = output_path / vertex_name; + if (!File::WriteStringToFile(PathToString(vertex_path), vertex_source)) + { + return nullptr; + } + + const auto pixel_path = output_path / pixel_name; + if (!File::WriteStringToFile(PathToString(pixel_path), pixel_source)) + { + return nullptr; + } + + source->AddAsset(metadata_path); + return source->GetAssetFromPath(metadata_path); +} +} // namespace GraphicsModEditor diff --git a/Source/Core/VideoCommon/GraphicsModEditor/ShaderGeneration.h b/Source/Core/VideoCommon/GraphicsModEditor/ShaderGeneration.h new file mode 100644 index 0000000000..890a494fb8 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/ShaderGeneration.h @@ -0,0 +1,32 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#include "VideoCommon/Assets/ShaderAsset.h" + +namespace GraphicsModEditor +{ +struct EditorAsset; +class EditorAssetSource; +struct ShaderGenerationContext +{ + VideoCommon::RasterShaderData shader_template; + + std::string_view pixel_name; + std::string_view vertex_name; + std::string_view metadata_name; +}; +EditorAsset* GenerateShader(const std::filesystem::path& output_path, + const ShaderGenerationContext& context, + GraphicsModEditor::EditorAssetSource* source); +EditorAsset* GenerateShader(const std::filesystem::path& output_path, std::string_view pixel_name, + std::string_view vertex_name, std::string_view metadata_name, + std::string_view pixel_source, std::string_view vertex_source, + std::string_view metadata_source, + GraphicsModEditor::EditorAssetSource* source); +} // namespace GraphicsModEditor diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.cpp index ef71ba6eb8..a7b8860367 100644 --- a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.cpp +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.cpp @@ -13,8 +13,9 @@ #include "VideoCommon/GraphicsModSystem/Constants.h" -std::optional GraphicsModConfig::Create(const std::string& file_path, - Source source) +namespace GraphicsModSystem::Config +{ +std::optional GraphicsMod::Create(const std::string& file_path) { picojson::value root; std::string error; @@ -25,186 +26,112 @@ std::optional GraphicsModConfig::Create(const std::string& fi return std::nullopt; } - GraphicsModConfig result; - if (!result.DeserializeFromConfig(root)) + GraphicsMod result; + if (!result.Deserialize(root)) { return std::nullopt; } - result.m_source = source; - if (source == Source::User) - { - const std::string base_path = File::GetUserPath(D_GRAPHICSMOD_IDX); - if (base_path.size() > file_path.size()) - { - ERROR_LOG_FMT( - VIDEO, - "Failed to load graphics mod json file '{}' due to it not matching the base path: {}", - file_path, base_path); - return std::nullopt; - } - result.m_relative_path = file_path.substr(base_path.size()); - } - else - { - const std::string base_path = File::GetSysDirectory() + DOLPHIN_SYSTEM_GRAPHICS_MOD_DIR; - if (base_path.size() > file_path.size()) - { - ERROR_LOG_FMT( - VIDEO, - "Failed to load graphics mod json file '{}' due to it not matching the base path: {}", - file_path, base_path); - return std::nullopt; - } - result.m_relative_path = file_path.substr(base_path.size()); - } - return result; } -std::optional GraphicsModConfig::Create(const picojson::object* obj) -{ - if (!obj) - return std::nullopt; - - const auto source_it = obj->find("source"); - if (source_it == obj->end()) - { - return std::nullopt; - } - const std::string source_str = source_it->second.to_str(); - - const auto path_it = obj->find("path"); - if (path_it == obj->end()) - { - return std::nullopt; - } - const std::string relative_path = path_it->second.to_str(); - - if (source_str == "system") - { - return Create(fmt::format("{}{}{}", File::GetSysDirectory(), DOLPHIN_SYSTEM_GRAPHICS_MOD_DIR, - relative_path), - Source::System); - } - else - { - return Create(File::GetUserPath(D_GRAPHICSMOD_IDX) + relative_path, Source::User); - } -} - -std::string GraphicsModConfig::GetAbsolutePath() const -{ - if (m_source == Source::System) - { - return WithUnifiedPathSeparators(fmt::format("{}{}{}", File::GetSysDirectory(), - DOLPHIN_SYSTEM_GRAPHICS_MOD_DIR, m_relative_path)); - } - else - { - return WithUnifiedPathSeparators(File::GetUserPath(D_GRAPHICSMOD_IDX) + m_relative_path); - } -} - -void GraphicsModConfig::SerializeToConfig(picojson::object& json_obj) const +void GraphicsMod::Serialize(picojson::object& json_obj) const { picojson::object serialized_metadata; + serialized_metadata.emplace("schema_version", static_cast(m_schema_version)); serialized_metadata.emplace("title", m_title); serialized_metadata.emplace("author", m_author); serialized_metadata.emplace("description", m_description); + serialized_metadata.emplace("mod_version", m_mod_version); json_obj.emplace("meta", std::move(serialized_metadata)); - picojson::array serialized_groups; - for (const auto& group : m_groups) - { - picojson::object serialized_group; - group.SerializeToConfig(serialized_group); - serialized_groups.emplace_back(std::move(serialized_group)); - } - json_obj.emplace("groups", std::move(serialized_groups)); - - picojson::array serialized_features; - for (const auto& feature : m_features) - { - picojson::object serialized_feature; - feature.SerializeToConfig(serialized_feature); - serialized_features.emplace_back(std::move(serialized_feature)); - } - json_obj.emplace("features", std::move(serialized_features)); - picojson::array serialized_assets; for (const auto& asset : m_assets) { picojson::object serialized_asset; - asset.SerializeToConfig(serialized_asset); + asset.Serialize(serialized_asset); serialized_assets.emplace_back(std::move(serialized_asset)); } json_obj.emplace("assets", std::move(serialized_assets)); + + picojson::array serialized_tags; + for (const auto& tag : m_tags) + { + picojson::object serialized_tag; + tag.Serialize(serialized_tag); + serialized_tags.emplace_back(std::move(serialized_tag)); + } + json_obj.emplace("tags", std::move(serialized_tags)); + + picojson::array serialized_targets; + for (const auto& target : m_targets) + { + picojson::object serialized_target; + SerializeTarget(serialized_target, target); + serialized_targets.emplace_back(std::move(serialized_target)); + } + json_obj.emplace("targets", std::move(serialized_targets)); + + picojson::array serialized_actions; + for (const auto& action : m_actions) + { + picojson::object serialized_action; + action.Serialize(serialized_action); + serialized_actions.emplace_back(std::move(serialized_action)); + } + json_obj.emplace("actions", std::move(serialized_actions)); + + picojson::object serialized_target_to_actions; + for (const auto& [target_index, action_indexes] : m_target_index_to_action_indexes) + { + picojson::array serialized_action_indexes; + for (const auto& action_index : action_indexes) + { + serialized_action_indexes.emplace_back(static_cast(action_index)); + } + serialized_target_to_actions.emplace(std::to_string(target_index), + std::move(serialized_action_indexes)); + } + json_obj.emplace("target_to_actions", serialized_target_to_actions); + + picojson::object serialized_tag_to_actions; + for (const auto& [tag_name, action_indexes] : m_tag_name_to_action_indexes) + { + picojson::array serialized_action_indexes; + for (const auto& action_index : action_indexes) + { + serialized_action_indexes.emplace_back(static_cast(action_index)); + } + serialized_tag_to_actions.emplace(tag_name, std::move(serialized_action_indexes)); + } + json_obj.emplace("tag_to_actions", serialized_tag_to_actions); + + picojson::object serialized_hash_policy; + serialized_hash_policy.emplace("attributes", + HashAttributesToString(m_default_hash_policy.attributes)); + json_obj.emplace("default_hash_policy", serialized_hash_policy); } -bool GraphicsModConfig::DeserializeFromConfig(const picojson::value& value) +bool GraphicsMod::Deserialize(const picojson::value& value) { const auto& meta = value.get("meta"); if (meta.is()) { - const auto& title = meta.get("title"); - if (title.is()) + const auto& meta_obj = meta.get(); + m_schema_version = ReadNumericFromJson(meta_obj, "schema_version").value_or(0); + if (m_schema_version != LATEST_SCHEMA_VERSION) { - m_title = title.to_str(); + // For now error, we can handle schema migrations in the future? + ERROR_LOG_FMT(VIDEO, + "Failed to deserialize graphics mod data, schema_version was '{}' but latest " + "version is '{}'", + m_schema_version, LATEST_SCHEMA_VERSION); + return false; } - const auto& author = meta.get("author"); - if (author.is()) - { - m_author = author.to_str(); - } - - const auto& description = meta.get("description"); - if (description.is()) - { - m_description = description.to_str(); - } - } - - const auto& groups = value.get("groups"); - if (groups.is()) - { - for (const auto& group_val : groups.get()) - { - if (!group_val.is()) - { - ERROR_LOG_FMT( - VIDEO, "Failed to load mod configuration file, specified group is not a json object"); - return false; - } - GraphicsTargetGroupConfig group; - if (!group.DeserializeFromConfig(group_val.get())) - { - return false; - } - - m_groups.push_back(std::move(group)); - } - } - - const auto& features = value.get("features"); - if (features.is()) - { - for (const auto& feature_val : features.get()) - { - if (!feature_val.is()) - { - ERROR_LOG_FMT( - VIDEO, "Failed to load mod configuration file, specified feature is not a json object"); - return false; - } - GraphicsModFeatureConfig feature; - if (!feature.DeserializeFromConfig(feature_val.get())) - { - return false; - } - - m_features.push_back(std::move(feature)); - } + m_title = ReadStringFromJson(meta_obj, "title").value_or("Unknown Mod"); + m_author = ReadStringFromJson(meta_obj, "author").value_or("Unknown"); + m_description = ReadStringFromJson(meta_obj, "description").value_or(""); + m_mod_version = ReadStringFromJson(meta_obj, "mod_version").value_or("v0.0.0"); } const auto& assets = value.get("assets"); @@ -218,8 +145,8 @@ bool GraphicsModConfig::DeserializeFromConfig(const picojson::value& value) VIDEO, "Failed to load mod configuration file, specified asset is not a json object"); return false; } - GraphicsModAssetConfig asset; - if (!asset.DeserializeFromConfig(asset_val.get())) + GraphicsModAsset asset; + if (!asset.Deserialize(asset_val.get())) { return false; } @@ -228,105 +155,147 @@ bool GraphicsModConfig::DeserializeFromConfig(const picojson::value& value) } } + const auto& tags = value.get("tags"); + if (tags.is()) + { + for (const auto& tag_val : tags.get()) + { + if (!tag_val.is()) + { + ERROR_LOG_FMT(VIDEO, + "Failed to load mod configuration file, specified tag is not a json object"); + return false; + } + GraphicsModTag tag; + if (!tag.Deserialize(tag_val.get())) + { + return false; + } + + m_tags.push_back(std::move(tag)); + } + } + + const auto& targets = value.get("targets"); + if (targets.is()) + { + for (const auto& target_val : targets.get()) + { + if (!target_val.is()) + { + ERROR_LOG_FMT( + VIDEO, "Failed to load mod configuration file, specified target is not a json object"); + return false; + } + + AnyTarget target; + if (!DeserializeTarget(target_val.get(), target)) + return false; + + m_targets.push_back(std::move(target)); + } + } + + const auto& actions = value.get("actions"); + if (actions.is()) + { + for (const auto& action_val : actions.get()) + { + if (!action_val.is()) + { + ERROR_LOG_FMT( + VIDEO, "Failed to load mod configuration file, specified action is not a json object"); + return false; + } + GraphicsModAction action; + if (!action.Deserialize(action_val.get())) + { + return false; + } + + m_actions.push_back(std::move(action)); + } + } + + const auto& target_to_actions = value.get("target_to_actions"); + if (target_to_actions.is()) + { + for (const auto& [key, action_indexes_val] : target_to_actions.get()) + { + u64 target_index = 0; + if (!TryParse(key, &target_index)) + { + ERROR_LOG_FMT( + VIDEO, "Failed to load mod configuration file, specified target index is not a number"); + return false; + } + + auto& action_indexes = m_target_index_to_action_indexes[target_index]; + if (!action_indexes_val.is()) + { + ERROR_LOG_FMT(VIDEO, + "Failed to load mod configuration file, specified target index '{}' has " + "a non-array action index value", + target_index); + } + + for (const auto& action_index_val : action_indexes_val.get()) + { + if (!action_index_val.is()) + { + ERROR_LOG_FMT(VIDEO, + "Failed to load mod configuration file, specified target index '{}' has " + "a non numeric action index", + target_index); + return false; + } + action_indexes.push_back(static_cast(action_index_val.get())); + } + } + } + + const auto& tag_to_actions = value.get("tag_to_actions"); + if (tag_to_actions.is()) + { + for (const auto& [tag, action_indexes_val] : tag_to_actions.get()) + { + auto& action_indexes = m_tag_name_to_action_indexes[tag]; + if (!action_indexes_val.is()) + { + ERROR_LOG_FMT(VIDEO, + "Failed to load mod configuration file, specified tag '{}' has " + "a non-array action index value", + tag); + } + + for (const auto& action_index_val : action_indexes_val.get()) + { + if (!action_index_val.is()) + { + ERROR_LOG_FMT(VIDEO, + "Failed to load mod configuration file, specified tag '{}' has " + "a non numeric action index", + tag); + return false; + } + action_indexes.push_back(static_cast(action_index_val.get())); + } + } + } + + m_default_hash_policy = GetDefaultHashPolicy(); + + const auto& default_hash_policy_json = value.get("default_hash_policy"); + if (default_hash_policy_json.is()) + { + auto& default_hash_policy_json_obj = default_hash_policy_json.get(); + const auto attributes = ReadStringFromJson(default_hash_policy_json_obj, "attributes"); + if (attributes) + { + m_default_hash_policy.attributes = HashAttributesFromString(*attributes); + } + } + return true; } - -void GraphicsModConfig::SerializeToProfile(picojson::object* obj) const -{ - if (!obj) - return; - - auto& json_obj = *obj; - switch (m_source) - { - case Source::User: - json_obj.emplace("source", "user"); - break; - case Source::System: - json_obj.emplace("source", "system"); - break; - } - - json_obj.emplace("path", m_relative_path); - - picojson::array serialized_groups; - for (const auto& group : m_groups) - { - picojson::object serialized_group; - group.SerializeToProfile(&serialized_group); - serialized_groups.emplace_back(std::move(serialized_group)); - } - json_obj.emplace("groups", std::move(serialized_groups)); - - picojson::array serialized_features; - for (const auto& feature : m_features) - { - picojson::object serialized_feature; - feature.SerializeToProfile(&serialized_feature); - serialized_features.emplace_back(std::move(serialized_feature)); - } - json_obj.emplace("features", std::move(serialized_features)); - - json_obj.emplace("enabled", m_enabled); - - json_obj.emplace("weight", static_cast(m_weight)); -} - -void GraphicsModConfig::DeserializeFromProfile(const picojson::object& obj) -{ - if (const auto it = obj.find("groups"); it != obj.end()) - { - if (it->second.is()) - { - const auto& serialized_groups = it->second.get(); - if (serialized_groups.size() != m_groups.size()) - return; - - for (std::size_t i = 0; i < serialized_groups.size(); i++) - { - const auto& serialized_group_val = serialized_groups[i]; - if (serialized_group_val.is()) - { - const auto& serialized_group = serialized_group_val.get(); - m_groups[i].DeserializeFromProfile(serialized_group); - } - } - } - } - - if (const auto it = obj.find("features"); it != obj.end()) - { - if (it->second.is()) - { - const auto& serialized_features = it->second.get(); - if (serialized_features.size() != m_features.size()) - return; - - for (std::size_t i = 0; i < serialized_features.size(); i++) - { - const auto& serialized_feature_val = serialized_features[i]; - if (serialized_feature_val.is()) - { - const auto& serialized_feature = serialized_feature_val.get(); - m_features[i].DeserializeFromProfile(serialized_feature); - } - } - } - } - - if (const auto it = obj.find("enabled"); it != obj.end()) - { - if (it->second.is()) - { - m_enabled = it->second.get(); - } - } - - if (const auto it = obj.find("weight"); it != obj.end()) - { - if (it->second.is()) - { - m_weight = static_cast(it->second.get()); - } - } -} +} // namespace GraphicsModSystem::Config diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.h b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.h index f845c612ad..bc34cb2186 100644 --- a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.h +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsMod.h @@ -3,44 +3,48 @@ #pragma once +#include #include #include #include #include -#include "VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.h" -#include "VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.h" -#include "VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.h" +#include "Common/CommonTypes.h" -struct GraphicsModConfig +#include "VideoCommon/GraphicsModSystem/Config/GraphicsModAction.h" +#include "VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.h" +#include "VideoCommon/GraphicsModSystem/Config/GraphicsModHashPolicy.h" +#include "VideoCommon/GraphicsModSystem/Config/GraphicsModTag.h" +#include "VideoCommon/GraphicsModSystem/Config/GraphicsTarget.h" + +namespace GraphicsModSystem::Config { +// Update this version when the hashing approach or data +// changes +static constexpr u16 LATEST_SCHEMA_VERSION = 1; + +struct GraphicsMod +{ + u16 m_schema_version = LATEST_SCHEMA_VERSION; + std::string m_title; std::string m_author; std::string m_description; - bool m_enabled = false; - u16 m_weight = 0; - std::string m_relative_path; + std::string m_mod_version; - enum class Source - { - User, - System - }; - Source m_source = Source::User; + std::vector m_assets; + std::vector m_tags; + std::vector m_targets; + std::vector m_actions; + std::map> m_target_index_to_action_indexes; + std::map> m_tag_name_to_action_indexes; - std::vector m_groups; - std::vector m_features; - std::vector m_assets; + HashPolicy m_default_hash_policy; - static std::optional Create(const std::string& file, Source source); - static std::optional Create(const picojson::object* obj); + static std::optional Create(const std::string& file); - std::string GetAbsolutePath() const; - - void SerializeToConfig(picojson::object& json_obj) const; - bool DeserializeFromConfig(const picojson::value& value); - - void SerializeToProfile(picojson::object* value) const; - void DeserializeFromProfile(const picojson::object& value); + void Serialize(picojson::object& json_obj) const; + bool Deserialize(const picojson::value& value); }; +} // namespace GraphicsModSystem::Config diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModAction.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModAction.cpp new file mode 100644 index 0000000000..971c7809b3 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModAction.cpp @@ -0,0 +1,36 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModSystem/Config/GraphicsModAction.h" + +#include "Common/JsonUtil.h" +#include "Common/Logging/Log.h" + +namespace GraphicsModSystem::Config +{ +void GraphicsModAction::Serialize(picojson::object& json_obj) const +{ + json_obj.emplace("factory_name", m_factory_name); + json_obj.emplace("data", m_data); +} + +bool GraphicsModAction::Deserialize(const picojson::object& obj) +{ + const auto factory_name = ReadStringFromJson(obj, "factory_name"); + if (!factory_name) + { + ERROR_LOG_FMT( + VIDEO, + "Failed to load mod configuration file, specified action's factory_name is not valid"); + return false; + } + m_factory_name = *factory_name; + + if (auto data_iter = obj.find("data"); data_iter != obj.end()) + { + m_data = data_iter->second; + } + + return true; +} +} // namespace GraphicsModSystem::Config diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModAction.h b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModAction.h new file mode 100644 index 0000000000..d4f8c9c965 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModAction.h @@ -0,0 +1,21 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include + +namespace GraphicsModSystem::Config +{ +struct GraphicsModAction +{ + std::string m_factory_name; + picojson::value m_data; + + void Serialize(picojson::object& json_obj) const; + bool Deserialize(const picojson::object& json_obj); +}; +} // namespace GraphicsModSystem::Config diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.cpp index 75840fcbf8..9d8f10fab9 100644 --- a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.cpp +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.cpp @@ -3,12 +3,15 @@ #include "VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.h" +#include "Common/JsonUtil.h" #include "Common/Logging/Log.h" #include "Common/StringUtil.h" -void GraphicsModAssetConfig::SerializeToConfig(picojson::object& json_obj) const +namespace GraphicsModSystem::Config { - json_obj.emplace("name", m_asset_id); +void GraphicsModAsset::Serialize(picojson::object& json_obj) const +{ + json_obj.emplace("id", m_asset_id); picojson::object serialized_data; for (const auto& [name, path] : m_map) @@ -18,21 +21,16 @@ void GraphicsModAssetConfig::SerializeToConfig(picojson::object& json_obj) const json_obj.emplace("data", std::move(serialized_data)); } -bool GraphicsModAssetConfig::DeserializeFromConfig(const picojson::object& obj) +bool GraphicsModAsset::Deserialize(const picojson::object& obj) { - auto name_iter = obj.find("name"); - if (name_iter == obj.end()) + const auto id = ReadStringFromJson(obj, "id"); + if (!id) { - ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, specified asset has no name"); + ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, specified asset has an id " + "that is not valid"); 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_asset_id = name_iter->second.to_str(); + m_asset_id = *id; auto data_iter = obj.find("data"); if (data_iter == obj.end()) @@ -64,3 +62,4 @@ bool GraphicsModAssetConfig::DeserializeFromConfig(const picojson::object& obj) return true; } +} // namespace GraphicsModSystem::Config diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.h b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.h index 53d8d71892..3ed29b6aa8 100644 --- a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.h +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.h @@ -7,13 +7,17 @@ #include -#include "VideoCommon/Assets/DirectFilesystemAssetLibrary.h" +#include "VideoCommon/Assets/CustomAssetLibrary.h" +#include "VideoCommon/Assets/Types.h" -struct GraphicsModAssetConfig +namespace GraphicsModSystem::Config +{ +struct GraphicsModAsset { VideoCommon::CustomAssetLibrary::AssetID m_asset_id; - VideoCommon::DirectFilesystemAssetLibrary::AssetMap m_map; + VideoCommon::Assets::AssetMap m_map; - void SerializeToConfig(picojson::object& json_obj) const; - bool DeserializeFromConfig(const picojson::object& obj); + void Serialize(picojson::object& json_obj) const; + bool Deserialize(const picojson::object& json_obj); }; +} // namespace GraphicsModSystem::Config diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.cpp deleted file mode 100644 index 62dc68e54c..0000000000 --- a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.cpp +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2022 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.h" - -#include "Common/Logging/Log.h" - -void GraphicsModFeatureConfig::SerializeToConfig(picojson::object& json_obj) const -{ - json_obj.emplace("group", m_group); - json_obj.emplace("action", m_action); - json_obj.emplace("action_data", m_action_data); -} - -bool GraphicsModFeatureConfig::DeserializeFromConfig(const picojson::object& obj) -{ - if (auto group_iter = obj.find("group"); group_iter != obj.end()) - { - if (!group_iter->second.is()) - { - ERROR_LOG_FMT( - VIDEO, - "Failed to load mod configuration file, specified feature's group is not a string"); - return false; - } - m_group = group_iter->second.get(); - } - - if (auto action_iter = obj.find("action"); action_iter != obj.end()) - { - if (!action_iter->second.is()) - { - ERROR_LOG_FMT( - VIDEO, - "Failed to load mod configuration file, specified feature's action is not a string"); - return false; - } - m_action = action_iter->second.get(); - } - - if (auto action_data_iter = obj.find("action_data"); action_data_iter != obj.end()) - { - m_action_data = action_data_iter->second; - } - - return true; -} - -void GraphicsModFeatureConfig::SerializeToProfile(picojson::object*) const -{ -} - -void GraphicsModFeatureConfig::DeserializeFromProfile(const picojson::object&) -{ -} diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.h b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.h deleted file mode 100644 index 5204eaef57..0000000000 --- a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.h +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2022 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include - -#include - -struct GraphicsModFeatureConfig -{ - std::string m_group; - std::string m_action; - picojson::value m_action_data; - - void SerializeToConfig(picojson::object& json_obj) const; - bool DeserializeFromConfig(const picojson::object& value); - - void SerializeToProfile(picojson::object* value) const; - void DeserializeFromProfile(const picojson::object& value); -}; diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.cpp index 3c234e9ec1..c1afdb5ccf 100644 --- a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.cpp +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.cpp @@ -8,6 +8,7 @@ #include #include +#include #include "Common/CommonPaths.h" #include "Common/FileSearch.h" @@ -15,49 +16,184 @@ #include "Common/JsonUtil.h" #include "Common/Logging/Log.h" #include "Common/StringUtil.h" +#include "Common/VariantUtil.h" #include "Core/ConfigManager.h" -#include "VideoCommon/GraphicsModSystem/Config/GraphicsMod.h" #include "VideoCommon/GraphicsModSystem/Constants.h" #include "VideoCommon/HiresTextures.h" -GraphicsModGroupConfig::GraphicsModGroupConfig(std::string game_id) : m_game_id(std::move(game_id)) +namespace GraphicsModSystem::Config +{ +GraphicsModGroup::GraphicsModGroup(std::string game_id) : m_game_id(std::move(game_id)) { } -GraphicsModGroupConfig::~GraphicsModGroupConfig() = default; - -GraphicsModGroupConfig::GraphicsModGroupConfig(const GraphicsModGroupConfig&) = default; - -GraphicsModGroupConfig::GraphicsModGroupConfig(GraphicsModGroupConfig&&) noexcept = default; - -GraphicsModGroupConfig& GraphicsModGroupConfig::operator=(const GraphicsModGroupConfig&) = default; - -GraphicsModGroupConfig& -GraphicsModGroupConfig::operator=(GraphicsModGroupConfig&&) noexcept = default; - -void GraphicsModGroupConfig::Load() +void GraphicsModGroup::Load() { - const std::string file_path = GetPath(); + struct GraphicsModWithDir + { + Config::GraphicsMod m_mod; + std::string m_path; + }; + std::vector action_only_mods; + std::vector target_only_mods; + const auto try_add_mod = [&](const std::string& dir) { + auto file = dir + DIR_SEP + "metadata.json"; + UnifyPathSeparators(file); - std::set known_paths; - if (File::Exists(file_path)) + if (auto mod = GraphicsMod::Create(file)) + { + if (mod->m_actions.empty() && mod->m_targets.empty()) + return; + + // Actions can be empty for mods that just define + // targets for use by other mods + // These are mainly used by Dolphin default mods + if (mod->m_actions.empty()) + { + GraphicsModWithDir mod_with_dir; + mod_with_dir.m_mod = std::move(*mod); + mod_with_dir.m_path = dir; + target_only_mods.push_back(std::move(mod_with_dir)); + return; + } + + // Targets can be empty for mods that define some actions + // and a tag + if (mod->m_targets.empty()) + { + GraphicsModWithDir mod_with_dir; + mod_with_dir.m_mod = std::move(*mod); + mod_with_dir.m_path = dir; + action_only_mods.push_back(std::move(mod_with_dir)); + return; + } + + std::string file_data; + if (!File::ReadFileToString(file, file_data)) + return; + GraphicsModGroup::GraphicsModWithMetadata mod_with_metadata; + mod_with_metadata.m_path = dir; + mod_with_metadata.m_mod = std::move(*mod); + mod_with_metadata.m_id = XXH3_64bits(file_data.data(), file_data.size()); + m_graphics_mods.push_back(std::move(mod_with_metadata)); + } + }; + + const std::set graphics_mod_user_directories = + GetTextureDirectoriesWithGameId(File::GetUserPath(D_GRAPHICSMOD_IDX), m_game_id); + + for (const auto& graphics_mod_directory : graphics_mod_user_directories) + { + try_add_mod(graphics_mod_directory); + } + + const std::set graphics_mod_system_directories = GetTextureDirectoriesWithGameId( + File::GetSysDirectory() + DOLPHIN_SYSTEM_GRAPHICS_MOD_DIR, m_game_id); + + for (const auto& graphics_mod_directory : graphics_mod_system_directories) + { + try_add_mod(graphics_mod_directory); + } + + // Now build some mods that are combination of multiple mods + // (typically used by Dolphin built-in mods) + for (const auto& action_only_mod_with_dir : action_only_mods) + { + // The way the action only mods interact with the target only mods is through tags + // If there are no tags, then no reason to care about this mod + if (action_only_mod_with_dir.m_mod.m_actions.empty() || + action_only_mod_with_dir.m_mod.m_tag_name_to_action_indexes.empty()) + { + continue; + } + + XXH3_state_t id_hash; + XXH3_INITSTATE(&id_hash); + XXH3_64bits_reset_withSeed(&id_hash, static_cast(1)); + + const auto action_only_file = action_only_mod_with_dir.m_path + DIR_SEP + "metadata.json"; + std::string action_only_file_data; + if (!File::ReadFileToString(action_only_file, action_only_file_data)) + continue; + XXH3_64bits_update(&id_hash, action_only_file_data.data(), action_only_file_data.size()); + + GraphicsMod combined_mod = action_only_mod_with_dir.m_mod; + + for (const auto& target_only_mod_with_dir : target_only_mods) + { + bool target_found = false; + for (const auto& target : target_only_mod_with_dir.m_mod.m_targets) + { + const auto contains_tag_name = [&](const GenericTarget& underlying_target) { + return std::any_of( + underlying_target.m_tag_names.begin(), underlying_target.m_tag_names.end(), + [&](const auto& tag_name) { + return action_only_mod_with_dir.m_mod.m_tag_name_to_action_indexes.contains( + tag_name); + }); + }; + + // Only be interested in this mod's target if the tag + // exists in the action mod's tag list + std::visit(overloaded{[&](const Config::IntTarget& int_target) { + if (contains_tag_name(int_target)) + { + combined_mod.m_targets.push_back(target); + target_found = true; + } + }, + [&](const Config::StringTarget& str_target) { + if (contains_tag_name(str_target)) + { + combined_mod.m_targets.push_back(target); + target_found = true; + } + }}, + target); + } + + if (target_found) + { + const auto file = target_only_mod_with_dir.m_path + DIR_SEP + "metadata.json"; + std::string target_file_data; + if (!File::ReadFileToString(file, target_file_data)) + continue; + XXH3_64bits_update(&id_hash, target_file_data.data(), target_file_data.size()); + } + } + + GraphicsModGroup::GraphicsModWithMetadata mod_with_metadata; + mod_with_metadata.m_path = action_only_mod_with_dir.m_path; + mod_with_metadata.m_mod = std::move(combined_mod); + mod_with_metadata.m_id = XXH3_64bits_digest(&id_hash); + m_graphics_mods.push_back(std::move(mod_with_metadata)); + } + + for (auto& mod : m_graphics_mods) + { + m_id_to_graphics_mod[mod.m_id] = &mod; + } + + const auto gameid_metadata = GetPath(); + if (File::Exists(gameid_metadata)) { picojson::value root; std::string error; - if (!JsonFromFile(file_path, &root, &error)) + if (!JsonFromFile(gameid_metadata, &root, &error)) { ERROR_LOG_FMT(VIDEO, "Failed to load graphics mod group json file '{}' due to parse error: {}", - file_path, error); + gameid_metadata, error); return; } + if (!root.is()) { ERROR_LOG_FMT( VIDEO, "Failed to load graphics mod group json file '{}' due to root not being an object!", - file_path); + gameid_metadata); return; } @@ -69,106 +205,73 @@ void GraphicsModGroupConfig::Load() if (mod_json.is()) { const auto& mod_json_obj = mod_json.get(); - auto graphics_mod = GraphicsModConfig::Create(&mod_json_obj); - if (!graphics_mod) + const auto id_str = ReadStringFromJson(mod_json_obj, "id"); + if (!id_str) + continue; + u64 id; + if (!TryParse(*id_str, &id)) { continue; } - graphics_mod->DeserializeFromProfile(mod_json_obj); - - auto mod_full_path = graphics_mod->GetAbsolutePath(); - known_paths.insert(std::move(mod_full_path)); - m_graphics_mods.push_back(std::move(*graphics_mod)); + if (const auto iter = m_id_to_graphics_mod.find(id); iter != m_id_to_graphics_mod.end()) + { + iter->second->m_weight = ReadNumericFromJson(mod_json_obj, "weight").value_or(0); + iter->second->m_enabled = ReadBoolFromJson(mod_json_obj, "enabled").value_or(false); + } } } } } - const auto try_add_mod = [&known_paths, this](const std::string& dir, - GraphicsModConfig::Source source) { - auto file = dir + DIR_SEP + "metadata.json"; - UnifyPathSeparators(file); - if (known_paths.contains(file)) - return; - - if (auto mod = GraphicsModConfig::Create(file, source)) - m_graphics_mods.push_back(std::move(*mod)); - }; - - const std::set graphics_mod_user_directories = - GetTextureDirectoriesWithGameId(File::GetUserPath(D_GRAPHICSMOD_IDX), m_game_id); - - for (const auto& graphics_mod_directory : graphics_mod_user_directories) - { - try_add_mod(graphics_mod_directory, GraphicsModConfig::Source::User); - } - - const std::set graphics_mod_system_directories = GetTextureDirectoriesWithGameId( - File::GetSysDirectory() + DOLPHIN_SYSTEM_GRAPHICS_MOD_DIR, m_game_id); - - for (const auto& graphics_mod_directory : graphics_mod_system_directories) - { - try_add_mod(graphics_mod_directory, GraphicsModConfig::Source::System); - } - - std::ranges::sort(m_graphics_mods, {}, &GraphicsModConfig::m_weight); - for (auto& mod : m_graphics_mods) - { - m_path_to_graphics_mod[mod.GetAbsolutePath()] = &mod; - } + std::ranges::sort(m_graphics_mods, {}, &GraphicsModGroup::GraphicsModWithMetadata::m_weight); m_change_count++; } -void GraphicsModGroupConfig::Save() const +void GraphicsModGroup::Save() const { - const std::string file_path = GetPath(); - std::ofstream json_stream; - File::OpenFStream(json_stream, file_path, std::ios_base::out); - if (!json_stream.is_open()) - { - ERROR_LOG_FMT(VIDEO, "Failed to open graphics mod group json file '{}' for writing", file_path); - return; - } - picojson::object serialized_root; picojson::array serialized_mods; - for (const auto& mod : m_graphics_mods) + for (const auto& [id, mod_ptr] : m_id_to_graphics_mod) { picojson::object serialized_mod; - mod.SerializeToProfile(&serialized_mod); + serialized_mod.emplace("id", std::to_string(id)); + serialized_mod.emplace("enabled", mod_ptr->m_enabled); + serialized_mod.emplace("weight", static_cast(mod_ptr->m_weight)); serialized_mods.emplace_back(std::move(serialized_mod)); } serialized_root.emplace("mods", std::move(serialized_mods)); - const auto output = picojson::value{serialized_root}.serialize(true); - json_stream << output; + const auto file_path = GetPath(); + if (!JsonToFile(file_path, picojson::value{serialized_root}, true)) + { + ERROR_LOG_FMT(VIDEO, "Failed to open graphics mod group json file '{}' for writing", file_path); + } } -void GraphicsModGroupConfig::SetChangeCount(u32 change_count) +void GraphicsModGroup::SetChangeCount(u32 change_count) { m_change_count = change_count; } -u32 GraphicsModGroupConfig::GetChangeCount() const +u32 GraphicsModGroup::GetChangeCount() const { return m_change_count; } -const std::vector& GraphicsModGroupConfig::GetMods() const +const std::vector& GraphicsModGroup::GetMods() const { return m_graphics_mods; } -std::vector& GraphicsModGroupConfig::GetMods() +std::vector& GraphicsModGroup::GetMods() { return m_graphics_mods; } -GraphicsModConfig* GraphicsModGroupConfig::GetMod(std::string_view absolute_path) const +GraphicsModGroup::GraphicsModWithMetadata* GraphicsModGroup::GetMod(u64 id) const { - if (const auto iter = m_path_to_graphics_mod.find(absolute_path); - iter != m_path_to_graphics_mod.end()) + if (const auto iter = m_id_to_graphics_mod.find(id); iter != m_id_to_graphics_mod.end()) { return iter->second; } @@ -176,13 +279,14 @@ GraphicsModConfig* GraphicsModGroupConfig::GetMod(std::string_view absolute_path return nullptr; } -const std::string& GraphicsModGroupConfig::GetGameID() const +const std::string& GraphicsModGroup::GetGameID() const { return m_game_id; } -std::string GraphicsModGroupConfig::GetPath() const +std::string GraphicsModGroup::GetPath() const { const std::string game_mod_root = File::GetUserPath(D_CONFIG_IDX) + GRAPHICSMOD_CONFIG_DIR; return fmt::format("{}/{}.json", game_mod_root, m_game_id); } +} // namespace GraphicsModSystem::Config diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.h b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.h index b2178db3f6..0e97a11cf2 100644 --- a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.h +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.h @@ -5,24 +5,17 @@ #include #include -#include #include #include "Common/CommonTypes.h" +#include "VideoCommon/GraphicsModSystem/Config/GraphicsMod.h" -struct GraphicsModConfig; - -class GraphicsModGroupConfig +namespace GraphicsModSystem::Config +{ +class GraphicsModGroup { public: - explicit GraphicsModGroupConfig(std::string game_id); - ~GraphicsModGroupConfig(); - - GraphicsModGroupConfig(const GraphicsModGroupConfig&); - GraphicsModGroupConfig(GraphicsModGroupConfig&&) noexcept; - - GraphicsModGroupConfig& operator=(const GraphicsModGroupConfig&); - GraphicsModGroupConfig& operator=(GraphicsModGroupConfig&&) noexcept; + explicit GraphicsModGroup(std::string game_id); void Load(); void Save() const; @@ -30,17 +23,27 @@ public: void SetChangeCount(u32 change_count); u32 GetChangeCount() const; - const std::vector& GetMods() const; - std::vector& GetMods(); + struct GraphicsModWithMetadata + { + GraphicsMod m_mod; + std::string m_path; + u64 m_id = 0; + bool m_enabled = false; + u16 m_weight = 0; + }; - GraphicsModConfig* GetMod(std::string_view absolute_path) const; + const std::vector& GetMods() const; + std::vector& GetMods(); + + GraphicsModWithMetadata* GetMod(u64 id) const; const std::string& GetGameID() const; private: std::string GetPath() const; std::string m_game_id; - std::vector m_graphics_mods; - std::map> m_path_to_graphics_mod; + std::vector m_graphics_mods; + std::map m_id_to_graphics_mod; u32 m_change_count = 0; }; +} // namespace GraphicsModSystem::Config diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModHashPolicy.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModHashPolicy.cpp new file mode 100644 index 0000000000..2169ab89a9 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModHashPolicy.cpp @@ -0,0 +1,117 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModSystem/Config/GraphicsModHashPolicy.h" + +#include "Common/Logging/Log.h" +#include "Common/StringUtil.h" + +namespace GraphicsModSystem::Config +{ +namespace +{ +bool ContainsAttribute(HashAttributes all_attributes, HashAttributes attribute) +{ + return static_cast>(all_attributes & attribute) != 0; +} + +HashAttributes GetDefaultHashAttributes() +{ + return HashAttributes::Blending | HashAttributes::Indices | HashAttributes::VertexLayout; +} +} // namespace + +HashAttributes operator|(HashAttributes lhs, HashAttributes rhs) +{ + return static_cast(static_cast>(lhs) | + static_cast>(rhs)); +} + +HashAttributes operator&(HashAttributes lhs, HashAttributes rhs) +{ + return static_cast(static_cast>(lhs) & + static_cast>(rhs)); +} + +HashPolicy GetDefaultHashPolicy() +{ + HashPolicy policy; + policy.attributes = GetDefaultHashAttributes(); + policy.first_texture_only = false; + policy.version = 1; + return policy; +} + +HashAttributes HashAttributesFromString(const std::string& str) +{ + if (str == "") + return GetDefaultHashAttributes(); + + HashAttributes attributes = static_cast(NoHashAttributes); + auto parts = SplitString(str, ','); + if (parts.empty()) + return GetDefaultHashAttributes(); + + for (auto& part : parts) + { + Common::ToLower(&part); + if (part == "blending") + { + attributes = attributes | HashAttributes::Blending; + } + else if (part == "projection") + { + attributes = attributes | HashAttributes::Projection; + } + else if (part == "vertex_position") + { + attributes = attributes | HashAttributes::VertexPosition; + } + else if (part == "vertex_texcoords") + { + attributes = attributes | HashAttributes::VertexTexCoords; + } + else if (part == "vertex_layout") + { + attributes = attributes | HashAttributes::VertexLayout; + } + else if (part == "indices") + { + attributes = attributes | HashAttributes::Indices; + } + } + if (static_cast(attributes) == NoHashAttributes) + return GetDefaultHashAttributes(); + return attributes; +} + +std::string HashAttributesToString(HashAttributes attributes) +{ + std::string result; + if (ContainsAttribute(attributes, HashAttributes::Blending)) + { + result += "blending,"; + } + if (ContainsAttribute(attributes, HashAttributes::Projection)) + { + result += "projection,"; + } + if (ContainsAttribute(attributes, HashAttributes::VertexPosition)) + { + result += "vertex_position,"; + } + if (ContainsAttribute(attributes, HashAttributes::VertexTexCoords)) + { + result += "vertex_texcoords,"; + } + if (ContainsAttribute(attributes, HashAttributes::VertexLayout)) + { + result += "vertex_layout,"; + } + if (ContainsAttribute(attributes, HashAttributes::Indices)) + { + result += "indices,"; + } + return result; +} +} // namespace GraphicsModSystem::Config diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModHashPolicy.h b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModHashPolicy.h new file mode 100644 index 0000000000..005167974d --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModHashPolicy.h @@ -0,0 +1,38 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "Common/CommonTypes.h" + +namespace GraphicsModSystem::Config +{ +constexpr u16 NoHashAttributes = 0; +enum class HashAttributes : u16 +{ + Blending = 1u << 1, + Projection = 1u << 2, + VertexPosition = 1u << 3, + VertexTexCoords = 1u << 4, + VertexLayout = 1u << 5, + Indices = 1u << 6 +}; + +struct HashPolicy +{ + HashAttributes attributes; + bool first_texture_only; + u64 version; +}; + +HashAttributes operator|(HashAttributes lhs, HashAttributes rhs); +HashAttributes operator&(HashAttributes lhs, HashAttributes rhs); + +HashPolicy GetDefaultHashPolicy(); +HashAttributes HashAttributesFromString(const std::string& str); +std::string HashAttributesToString(HashAttributes attributes); + +} // namespace GraphicsModSystem::Config diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModTag.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModTag.cpp new file mode 100644 index 0000000000..c059fa3060 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModTag.cpp @@ -0,0 +1,31 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModSystem/Config/GraphicsModTag.h" + +#include "Common/JsonUtil.h" +#include "Common/Logging/Log.h" + +namespace GraphicsModSystem::Config +{ +void GraphicsModTag::Serialize(picojson::object& obj) const +{ + obj.emplace("name", m_name); + obj.emplace("description", m_description); +} + +bool GraphicsModTag::Deserialize(const picojson::object& obj) +{ + const auto name = ReadStringFromJson(obj, "name"); + if (!name) + { + ERROR_LOG_FMT(VIDEO, + "Failed to load mod configuration file, specified tag has an invalid name"); + return false; + } + m_name = *name; + m_description = ReadStringFromJson(obj, "description").value_or(""); + + return true; +} +} // namespace GraphicsModSystem::Config diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModTag.h b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModTag.h new file mode 100644 index 0000000000..055db11f9a --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModTag.h @@ -0,0 +1,23 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include + +#include "Common/Matrix.h" + +namespace GraphicsModSystem::Config +{ +struct GraphicsModTag +{ + std::string m_name; + std::string m_description; + Common::Vec3 m_color; + + void Serialize(picojson::object& json_obj) const; + bool Deserialize(const picojson::object& json_obj); +}; +} // namespace GraphicsModSystem::Config diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTarget.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTarget.cpp index b9f5ef8f29..0cdf80360a 100644 --- a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTarget.cpp +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTarget.cpp @@ -3,308 +3,134 @@ #include "VideoCommon/GraphicsModSystem/Config/GraphicsTarget.h" +#include "Common/JsonUtil.h" #include "Common/Logging/Log.h" #include "Common/StringUtil.h" #include "Common/VariantUtil.h" -#include "VideoCommon/TextureCacheBase.h" -namespace +namespace GraphicsModSystem::Config { -template , int> = 0> -std::optional DeserializeFBTargetFromConfig(const picojson::object& obj, std::string_view prefix) -{ - T fb; - const auto texture_filename_iter = obj.find("texture_filename"); - if (texture_filename_iter == obj.end()) - { - ERROR_LOG_FMT(VIDEO, - "Failed to load mod configuration file, option 'texture_filename' not found"); - return std::nullopt; - } - if (!texture_filename_iter->second.is()) - { - ERROR_LOG_FMT( - VIDEO, - "Failed to load mod configuration file, option 'texture_filename' is not a string type"); - return std::nullopt; - } - const auto texture_filename = texture_filename_iter->second.get(); - const auto texture_filename_without_prefix = texture_filename.substr(prefix.size() + 1); - const auto split_str_values = SplitString(texture_filename_without_prefix, '_'); - if (split_str_values.size() == 1) - { - ERROR_LOG_FMT( - VIDEO, "Failed to load mod configuration file, value in 'texture_filename' is not valid"); - return std::nullopt; - } - const auto split_width_height_values = SplitString(texture_filename_without_prefix, 'x'); - if (split_width_height_values.size() != 2) - { - ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' is " - "not valid, width and height separator found more matches than expected"); - return std::nullopt; - } - - const std::size_t width_underscore_pos = split_width_height_values[0].find_last_of('_'); - std::string width_str; - if (width_underscore_pos == std::string::npos) - { - width_str = split_width_height_values[0]; - } - else - { - width_str = split_width_height_values[0].substr(width_underscore_pos + 1); - } - if (!TryParse(width_str, &fb.m_width)) - { - ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' is " - "not valid, width not a number"); - return std::nullopt; - } - - const std::size_t height_underscore_pos = split_width_height_values[1].find_first_of('_'); - if (height_underscore_pos == std::string::npos || - height_underscore_pos == split_width_height_values[1].size() - 1) - { - ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' is " - "not valid, underscore after height is missing or incomplete"); - return std::nullopt; - } - const std::string height_str = split_width_height_values[1].substr(0, height_underscore_pos); - if (!TryParse(height_str, &fb.m_height)) - { - ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' is " - "not valid, height not a number"); - return std::nullopt; - } - - const std::size_t format_underscore_pos = - split_width_height_values[1].find_first_of('_', height_underscore_pos + 1); - - std::string format_str; - if (format_underscore_pos == std::string::npos) - { - format_str = split_width_height_values[1].substr(height_underscore_pos + 1); - } - else - { - format_str = split_width_height_values[1].substr( - height_underscore_pos + 1, (format_underscore_pos - height_underscore_pos) - 1); - } - u32 format; - if (!TryParse(format_str, &format)) - { - ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' is " - "not valid, texture format is not a number"); - return std::nullopt; - } - if (!IsValidTextureFormat(static_cast(format))) - { - ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, value in 'texture_filename' is " - "not valid, texture format is not valid"); - return std::nullopt; - } - fb.m_texture_format = static_cast(format); - - return fb; -} -std::optional ExtractTextureFilenameForConfig(const picojson::object& obj) -{ - const auto texture_filename_iter = obj.find("texture_filename"); - if (texture_filename_iter == obj.end()) - { - ERROR_LOG_FMT(VIDEO, - "Failed to load mod configuration file, option 'texture_filename' not found"); - return std::nullopt; - } - if (!texture_filename_iter->second.is()) - { - ERROR_LOG_FMT( - VIDEO, - "Failed to load mod configuration file, option 'texture_filename' is not a string type"); - return std::nullopt; - } - std::string texture_info = texture_filename_iter->second.get(); - - const auto handle_fb_texture = - [&texture_info](std::string_view type) -> std::optional { - const auto letter_n_pos = texture_info.find("_n"); - if (letter_n_pos == std::string::npos) - { - ERROR_LOG_FMT(VIDEO, - "Failed to load mod configuration file, value in 'texture_filename' " - "is {} without a count", - type); - return std::nullopt; - } - - const auto post_underscore = texture_info.find_first_of('_', letter_n_pos + 2); - if (post_underscore == std::string::npos) - return texture_info.erase(letter_n_pos, texture_info.size() - letter_n_pos); - else - return texture_info.erase(letter_n_pos, post_underscore - letter_n_pos); - }; - - if (texture_info.starts_with(EFB_DUMP_PREFIX)) - return handle_fb_texture("an efb"); - else if (texture_info.starts_with(XFB_DUMP_PREFIX)) - return handle_fb_texture("a xfb"); - return texture_info; -} -} // namespace - -void SerializeTargetToConfig(picojson::object& json_obj, const GraphicsTargetConfig& target) +void SerializeTarget(picojson::object& json_obj, const AnyTarget& target) { std::visit(overloaded{ - [&](const DrawStartedTextureTarget& the_target) { - json_obj.emplace("type", "draw_started"); - json_obj.emplace("texture_filename", the_target.m_texture_info_string); + [&](const IntTarget& the_target) { + json_obj.emplace("type", "int"); + json_obj.emplace("id", std::to_string(the_target.m_target_id)); + json_obj.emplace("name", the_target.m_name); + json_obj.emplace("tags", ToJsonArray(the_target.m_tag_names)); }, - [&](const LoadTextureTarget& the_target) { - json_obj.emplace("type", "load_texture"); - json_obj.emplace("texture_filename", the_target.m_texture_info_string); - }, - [&](const CreateTextureTarget& the_target) { - json_obj.emplace("type", "create_texture"); - json_obj.emplace("texture_filename", the_target.m_texture_info_string); - }, - [&](const EFBTarget& the_target) { - json_obj.emplace("type", "efb"); - json_obj.emplace("texture_filename", - fmt::format("{}_{}x{}_{}", EFB_DUMP_PREFIX, the_target.m_width, - the_target.m_height, - static_cast(the_target.m_texture_format))); - }, - [&](const XFBTarget& the_target) { - json_obj.emplace("type", "xfb"); - json_obj.emplace("texture_filename", - fmt::format("{}_{}x{}_{}", XFB_DUMP_PREFIX, the_target.m_width, - the_target.m_height, - static_cast(the_target.m_texture_format))); - }, - [&](const ProjectionTarget& the_target) { - const char* type_name = "3d"; - if (the_target.m_projection_type == ProjectionType::Orthographic) - type_name = "2d"; - - json_obj.emplace("type", type_name); - - if (the_target.m_texture_info_string) - { - json_obj.emplace("texture_filename", *the_target.m_texture_info_string); - } + [&](const StringTarget& the_target) { + json_obj.emplace("type", "string"); + json_obj.emplace("id", the_target.m_target_id); + json_obj.emplace("name", the_target.m_name); + json_obj.emplace("tags", ToJsonArray(the_target.m_tag_names)); }, }, target); } - -std::optional DeserializeTargetFromConfig(const picojson::object& obj) +bool DeserializeTarget(const picojson::object& json_obj, AnyTarget& target) { - const auto type_iter = obj.find("type"); - if (type_iter == obj.end()) - { - ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, option 'type' not found"); - return std::nullopt; - } - if (!type_iter->second.is()) + const auto type = ReadStringFromJson(json_obj, "type"); + if (!type) { ERROR_LOG_FMT(VIDEO, - "Failed to load mod configuration file, option 'type' is not a string type"); - return std::nullopt; + "Failed to load mod configuration file, option 'type' was missing or invalid"); + return false; } - const std::string& type = type_iter->second.get(); - if (type == "draw_started") - { - std::optional texture_info = ExtractTextureFilenameForConfig(obj); - if (!texture_info.has_value()) - return std::nullopt; - DrawStartedTextureTarget target; - target.m_texture_info_string = texture_info.value(); - return target; - } - else if (type == "load_texture") + if (*type == "int") { - std::optional texture_info = ExtractTextureFilenameForConfig(obj); - if (!texture_info.has_value()) - return std::nullopt; + IntTarget i_target; + i_target.m_name = ReadStringFromJson(json_obj, "name").value_or(""); - LoadTextureTarget target; - target.m_texture_info_string = texture_info.value(); - return target; - } - else if (type == "create_texture") - { - std::optional texture_info = ExtractTextureFilenameForConfig(obj); - if (!texture_info.has_value()) - return std::nullopt; - - CreateTextureTarget target; - target.m_texture_info_string = texture_info.value(); - return target; - } - else if (type == "efb") - { - return DeserializeFBTargetFromConfig(obj, EFB_DUMP_PREFIX); - } - else if (type == "xfb") - { - return DeserializeFBTargetFromConfig(obj, EFB_DUMP_PREFIX); - } - else if (type == "projection") - { - ProjectionTarget target; - const auto texture_iter = obj.find("texture_filename"); - if (texture_iter != obj.end()) - { - std::optional texture_info = ExtractTextureFilenameForConfig(obj); - if (!texture_info.has_value()) - return std::nullopt; - target.m_texture_info_string = texture_info; - } - const auto value_iter = obj.find("value"); - if (value_iter == obj.end()) - { - ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, option 'value' not found"); - return std::nullopt; - } - if (!value_iter->second.is()) + const std::string target_id_str = ReadStringFromJson(json_obj, "id").value_or("0"); + u64 target_id = 0; + if (!TryParse(target_id_str, &target_id)) { ERROR_LOG_FMT(VIDEO, - "Failed to load mod configuration file, option 'value' is not a string type"); - return std::nullopt; + "Failed to load graphics mod configuration file, option 'id' is invalid"); + return false; } - const auto& value_str = value_iter->second.get(); - if (value_str == "2d") + i_target.m_target_id = target_id; + if (i_target.m_target_id == 0) { - target.m_projection_type = ProjectionType::Orthographic; + ERROR_LOG_FMT(VIDEO, + "Failed to load graphics mod configuration file, option 'id' is invalid"); + return false; } - else if (value_str == "3d") + if (const auto tags_iter = json_obj.find("tags"); tags_iter != json_obj.end()) { - target.m_projection_type = ProjectionType::Perspective; + if (!tags_iter->second.is()) + { + ERROR_LOG_FMT( + VIDEO, + "Failed to load graphics mod configuration file, option 'tags' is not an array type"); + return false; + } + + const auto& tags_json = tags_iter->second.get(); + if (!std::all_of(tags_json.begin(), tags_json.end(), + [](const picojson::value& value) { return value.is(); })) + { + ERROR_LOG_FMT(VIDEO, + "Failed to load graphics mod configuration file, all tags are not strings"); + return false; + } + + for (const auto& tag_json : tags_json) + { + i_target.m_tag_names.push_back(tag_json.to_str()); + } } - else + target = i_target; + } + else if (*type == "string") + { + StringTarget s_target; + s_target.m_name = ReadStringFromJson(json_obj, "name").value_or(""); + + const auto id = ReadStringFromJson(json_obj, "id"); + if (!id) { - ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, option 'value' is not a valid " - "value, valid values are: 2d, 3d"); - return std::nullopt; + ERROR_LOG_FMT(VIDEO, + "Failed to load graphics mod configuration file, option 'id' is invalid"); + return false; } - return target; + s_target.m_target_id = *id; + if (const auto tags_iter = json_obj.find("tags"); tags_iter != json_obj.end()) + { + if (!tags_iter->second.is()) + { + ERROR_LOG_FMT( + VIDEO, + "Failed to load graphics mod configuration file, option 'tags' is not an array type"); + return false; + } + + const auto& tags_json = tags_iter->second.get(); + if (!std::all_of(tags_json.begin(), tags_json.end(), + [](const picojson::value& value) { return value.is(); })) + { + ERROR_LOG_FMT(VIDEO, + "Failed to load graphics mod configuration file, all tags are not strings"); + return false; + } + + for (const auto& tag_json : tags_json) + { + s_target.m_tag_names.push_back(tag_json.to_str()); + } + } + target = s_target; } else { - ERROR_LOG_FMT(VIDEO, - "Failed to load mod configuration file, option 'type' is not a valid value"); + ERROR_LOG_FMT( + VIDEO, + "Failed to load graphics mod configuration file, option 'type' is invalid value '{}'", + *type); + return false; } - return std::nullopt; -} - -void SerializeTargetToProfile(picojson::object*, const GraphicsTargetConfig&) -{ - // Added for consistency, no functionality as of now -} - -void DeserializeTargetFromProfile(const picojson::object&, GraphicsTargetConfig*) -{ - // Added for consistency, no functionality as of now + return true; } +} // namespace GraphicsModSystem::Config diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTarget.h b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTarget.h index 740da00727..ce9405732f 100644 --- a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTarget.h +++ b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTarget.h @@ -3,60 +3,34 @@ #pragma once -#include #include #include +#include #include #include "Common/CommonTypes.h" -#include "VideoCommon/TextureDecoder.h" -#include "VideoCommon/XFMemory.h" -struct TextureTarget +namespace GraphicsModSystem::Config { - std::string m_texture_info_string; +struct GenericTarget +{ + std::string m_name; + std::vector m_tag_names; }; -struct DrawStartedTextureTarget final : public TextureTarget +struct StringTarget final : public GenericTarget { + std::string m_target_id; }; -struct LoadTextureTarget final : public TextureTarget +struct IntTarget final : public GenericTarget { + u64 m_target_id; }; -struct CreateTextureTarget final : public TextureTarget -{ -}; +using AnyTarget = std::variant; -struct FBTarget -{ - u32 m_height = 0; - u32 m_width = 0; - TextureFormat m_texture_format = TextureFormat::I4; -}; - -struct EFBTarget final : public FBTarget -{ -}; - -struct XFBTarget final : public FBTarget -{ -}; - -struct ProjectionTarget -{ - std::optional m_texture_info_string; - ProjectionType m_projection_type = ProjectionType::Perspective; -}; - -using GraphicsTargetConfig = - std::variant; - -void SerializeTargetToConfig(picojson::object& json_obj, const GraphicsTargetConfig& target); -std::optional DeserializeTargetFromConfig(const picojson::object& obj); - -void SerializeTargetToProfile(picojson::object* obj, const GraphicsTargetConfig& target); -void DeserializeTargetFromProfile(const picojson::object& obj, GraphicsTargetConfig* target); +void SerializeTarget(picojson::object& json_obj, const AnyTarget& target); +bool DeserializeTarget(const picojson::object& json_obj, AnyTarget& target); +} // namespace GraphicsModSystem::Config diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.cpp deleted file mode 100644 index 3ac1b108cd..0000000000 --- a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.cpp +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2022 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.h" - -#include "Common/Logging/Log.h" - -void GraphicsTargetGroupConfig::SerializeToConfig(picojson::object& json_obj) const -{ - picojson::array serialized_targets; - for (const auto& target : m_targets) - { - picojson::object serialized_target; - SerializeTargetToConfig(serialized_target, target); - serialized_targets.emplace_back(std::move(serialized_target)); - } - json_obj.emplace("targets", std::move(serialized_targets)); - json_obj.emplace("name", m_name); -} - -bool GraphicsTargetGroupConfig::DeserializeFromConfig(const picojson::object& obj) -{ - if (auto name_iter = obj.find("name"); name_iter != obj.end()) - { - if (!name_iter->second.is()) - { - ERROR_LOG_FMT( - VIDEO, "Failed to load mod configuration file, specified group's name is not a string"); - return false; - } - m_name = name_iter->second.get(); - } - - if (auto targets_iter = obj.find("targets"); targets_iter != obj.end()) - { - if (!targets_iter->second.is()) - { - ERROR_LOG_FMT( - VIDEO, - "Failed to load mod configuration file, specified group's targets is not an array"); - return false; - } - for (const auto& target_val : targets_iter->second.get()) - { - if (!target_val.is()) - { - ERROR_LOG_FMT( - VIDEO, - "Failed to load shader configuration file, specified target is not a json object"); - return false; - } - const auto target = DeserializeTargetFromConfig(target_val.get()); - if (!target) - { - return false; - } - - m_targets.push_back(*target); - } - } - - return true; -} - -void GraphicsTargetGroupConfig::SerializeToProfile(picojson::object* obj) const -{ - if (!obj) - return; - auto& json_obj = *obj; - picojson::array serialized_targets; - for (const auto& target : m_targets) - { - picojson::object serialized_target; - SerializeTargetToProfile(&serialized_target, target); - serialized_targets.emplace_back(std::move(serialized_target)); - } - json_obj.emplace("targets", std::move(serialized_targets)); -} - -void GraphicsTargetGroupConfig::DeserializeFromProfile(const picojson::object& obj) -{ - if (const auto it = obj.find("targets"); it != obj.end()) - { - if (it->second.is()) - { - const auto& serialized_targets = it->second.get(); - if (serialized_targets.size() != m_targets.size()) - return; - - for (std::size_t i = 0; i < serialized_targets.size(); i++) - { - const auto& serialized_target_val = serialized_targets[i]; - if (serialized_target_val.is()) - { - const auto& serialized_target = serialized_target_val.get(); - DeserializeTargetFromProfile(serialized_target, &m_targets[i]); - } - } - } - } -} diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.h b/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.h deleted file mode 100644 index d0034ef30b..0000000000 --- a/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.h +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2022 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include - -#include - -#include "VideoCommon/GraphicsModSystem/Config/GraphicsTarget.h" - -struct GraphicsTargetGroupConfig -{ - std::string m_name; - std::vector m_targets; - - void SerializeToConfig(picojson::object& json_obj) const; - bool DeserializeFromConfig(const picojson::object& obj); - - void SerializeToProfile(picojson::object* obj) const; - void DeserializeFromProfile(const picojson::object& obj); -}; diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Constants.h b/Source/Core/VideoCommon/GraphicsModSystem/Constants.h index 5c73c3e962..3828550798 100644 --- a/Source/Core/VideoCommon/GraphicsModSystem/Constants.h +++ b/Source/Core/VideoCommon/GraphicsModSystem/Constants.h @@ -4,8 +4,14 @@ #pragma once #include +#include #include "Common/CommonPaths.h" static const inline std::string DOLPHIN_SYSTEM_GRAPHICS_MOD_DIR = LOAD_DIR DIR_SEP GRAPHICSMOD_DIR DIR_SEP; + +namespace GraphicsModSystem +{ +static constexpr std::string_view internal_tag_prefix = "__internal_"; +} diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/CustomMeshAction.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/CustomMeshAction.cpp new file mode 100644 index 0000000000..2676c6c753 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/CustomMeshAction.cpp @@ -0,0 +1,231 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModSystem/Runtime/Actions/CustomMeshAction.h" + +#include +#include + +#include "Common/JsonUtil.h" +#include "Core/System.h" + +#include "VideoCommon/GraphicsModEditor/Controls/AssetDisplay.h" +#include "VideoCommon/GraphicsModEditor/EditorEvents.h" +#include "VideoCommon/GraphicsModEditor/EditorMain.h" +#include "VideoCommon/GraphicsModSystem/Runtime/CustomResourceManager.h" + +std::unique_ptr +CustomMeshAction::Create(const picojson::value& json_data, + std::shared_ptr library) +{ + VideoCommon::CustomAssetLibrary::AssetID mesh_asset; + + if (!json_data.is()) + return nullptr; + + const auto& obj = json_data.get(); + + if (const auto it = obj.find("mesh_asset"); it != obj.end()) + { + if (it->second.is()) + { + mesh_asset = it->second.to_str(); + } + } + + Common::Vec3 scale{1, 1, 1}; + if (const auto it = obj.find("scale"); it != obj.end()) + { + if (it->second.is()) + { + FromJson(it->second.get(), scale); + } + } + + Common::Vec3 translation{}; + if (const auto it = obj.find("translation"); it != obj.end()) + { + if (it->second.is()) + { + FromJson(it->second.get(), translation); + } + } + + Common::Vec3 rotation{}; + if (const auto it = obj.find("rotation"); it != obj.end()) + { + if (it->second.is()) + { + FromJson(it->second.get(), rotation); + } + } + + return std::make_unique(std::move(library), std::move(rotation), + std::move(scale), std::move(translation), + std::move(mesh_asset)); +} + +CustomMeshAction::CustomMeshAction(std::shared_ptr library) + : m_library(std::move(library)) +{ +} + +CustomMeshAction::CustomMeshAction(std::shared_ptr library, + Common::Vec3 rotation, Common::Vec3 scale, + Common::Vec3 translation, + VideoCommon::CustomAssetLibrary::AssetID mesh_asset_id) + : m_library(std::move(library)), m_mesh_asset_id(std::move(mesh_asset_id)), + m_scale(std::move(scale)), m_rotation(std::move(rotation)), + m_translation(std::move(translation)) +{ + m_transform_changed = true; +} + +CustomMeshAction::~CustomMeshAction() = default; + +void CustomMeshAction::OnDrawStarted(GraphicsModActionData::DrawStarted* draw_started) +{ + if (!draw_started) [[unlikely]] + return; + + if (!draw_started->material) [[unlikely]] + return; + + if (m_mesh_asset_id.empty()) + return; + + /*if (m_recalculate_original_mesh_center) + { + auto vert_ptr = draw_started->draw_data_view.vertex_data.data(); + Common::Vec3 center_point{}; + for (std::size_t vert_index = 0; vert_index < draw_started->draw_data_view.vertex_data.size(); + vert_index++) + { + float vx = 0; + std::memcpy(&vx, vert_ptr, sizeof(float)); + center_point.x += vx; + + float vy = 0; + std::memcpy(&vy, vert_ptr + sizeof(float), sizeof(float)); + center_point.y += vy; + + float vz = 0; + std::memcpy(&vz, vert_ptr + sizeof(float) * 2, sizeof(float)); + center_point.z += vz; + + vert_ptr += draw_started->draw_data_view.vertex_format->GetVertexStride(); + } + center_point.x /= draw_started->draw_data_view.vertex_data.size(); + center_point.y /= draw_started->draw_data_view.vertex_data.size(); + center_point.z /= draw_started->draw_data_view.vertex_data.size(); + m_original_mesh_center = center_point; + }*/ + + if (m_transform_changed) + { + const auto scale = Common::Matrix33::Scale(m_scale); + const auto rotation = Common::Quaternion::RotateXYZ(m_rotation); + m_calculated_transform = Common::Matrix44::FromMatrix33(scale) * + Common::Matrix44::FromQuaternion(rotation) * + Common::Matrix44::Translate(m_translation); + m_transform_changed = false; + } + + auto& resource_manager = Core::System::GetInstance().GetCustomResourceManager(); + + const auto mesh = + resource_manager.GetMeshFromAsset(m_mesh_asset_id, m_library, draw_started->draw_data_view); + *draw_started->mesh = mesh; + + if (mesh) + { + *draw_started->transform = m_calculated_transform; + *draw_started->ignore_mesh_transform = m_ignore_mesh_transform; + } +} + +void CustomMeshAction::DrawImGui() +{ + auto& editor = Core::System::GetInstance().GetGraphicsModEditor(); + if (ImGui::CollapsingHeader("Custom mesh", ImGuiTreeNodeFlags_DefaultOpen)) + { + if (ImGui::BeginTable("CustomMeshForm", 2)) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Mesh"); + ImGui::TableNextColumn(); + if (GraphicsModEditor::Controls::AssetDisplay("MeshValue", editor.GetEditorState(), + &m_mesh_asset_id, GraphicsModEditor::Mesh)) + { + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(m_mesh_asset_id); + m_mesh_asset_changed = true; + } + ImGui::EndTable(); + } + } + if (ImGui::CollapsingHeader("Custom mesh transform", ImGuiTreeNodeFlags_DefaultOpen)) + { + if (ImGui::BeginTable("CustomMeshTransform", 2)) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Scale"); + ImGui::TableNextColumn(); + if (ImGui::InputFloat3("##Scale", m_scale.data.data())) + { + GraphicsModEditor::EditorEvents::ChangeOccurredEvent::Trigger(); + m_transform_changed = true; + } + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Rotation"); + ImGui::TableNextColumn(); + if (ImGui::InputFloat3("##Rotation", m_rotation.data.data())) + { + GraphicsModEditor::EditorEvents::ChangeOccurredEvent::Trigger(); + m_transform_changed = true; + } + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Translate"); + ImGui::TableNextColumn(); + if (ImGui::InputFloat3("##Translate", m_translation.data.data())) + { + GraphicsModEditor::EditorEvents::ChangeOccurredEvent::Trigger(); + m_transform_changed = true; + } + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Ignore Mesh Transform"); + ImGui::TableNextColumn(); + ImGui::SetTooltip( + "Ignore any set mesh transform and use only apply the game's transform, this " + "can be useful when making simple model edits with mesh dumped from Dolphin"); + if (ImGui::Checkbox("##IgnoreMeshTransform", &m_ignore_mesh_transform)) + { + GraphicsModEditor::EditorEvents::ChangeOccurredEvent::Trigger(); + m_transform_changed = true; + } + ImGui::EndTable(); + } + } +} + +void CustomMeshAction::SerializeToConfig(picojson::object* obj) +{ + if (!obj) [[unlikely]] + return; + + auto& json_obj = *obj; + json_obj.emplace("translation", ToJsonObject(m_translation)); + json_obj.emplace("scale", ToJsonObject(m_scale)); + json_obj.emplace("rotation", ToJsonObject(m_rotation)); + json_obj.emplace("mesh_asset", m_mesh_asset_id); + json_obj.emplace("ignore_mesh_transform", m_ignore_mesh_transform); +} + +std::string CustomMeshAction::GetFactoryName() const +{ + return std::string{factory_name}; +} diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/CustomMeshAction.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/CustomMeshAction.h new file mode 100644 index 0000000000..c799472df8 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/CustomMeshAction.h @@ -0,0 +1,56 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include + +#include + +#include "Common/Matrix.h" +#include "Common/SmallVector.h" + +#include "VideoCommon/AbstractTexture.h" +#include "VideoCommon/Assets/CustomAssetLibrary.h" +#include "VideoCommon/Assets/MeshAsset.h" +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h" +#include "VideoCommon/NativeVertexFormat.h" +#include "VideoCommon/ShaderGenCommon.h" + +class CustomMeshAction final : public GraphicsModAction +{ +public: + static constexpr std::string_view factory_name = "custom_mesh"; + static std::unique_ptr + Create(const picojson::value& json_data, + std::shared_ptr library); + explicit CustomMeshAction(std::shared_ptr library); + CustomMeshAction(std::shared_ptr library, Common::Vec3 rotation, + Common::Vec3 scale, Common::Vec3 translation, + VideoCommon::CustomAssetLibrary::AssetID mesh_asset_id); + ~CustomMeshAction(); + void OnDrawStarted(GraphicsModActionData::DrawStarted*) override; + + void DrawImGui() override; + void SerializeToConfig(picojson::object* obj) override; + std::string GetFactoryName() const override; + +private: + std::shared_ptr m_library; + VideoCommon::CustomAssetLibrary::AssetID m_mesh_asset_id; + VideoCommon::CachedAsset m_cached_mesh_asset; + + Common::Vec3 m_scale = Common::Vec3{1, 1, 1}; + Common::Vec3 m_rotation = Common::Vec3{0, 0, 0}; + Common::Vec3 m_translation = Common::Vec3{0, 0, 0}; + Common::Vec3 m_original_mesh_center = Common::Vec3{0, 0, 0}; + bool m_transform_changed = false; + bool m_mesh_asset_changed = false; + bool m_recalculate_original_mesh_center = true; + bool m_ignore_mesh_transform = false; + Common::Matrix44 m_calculated_transform; +}; diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.cpp index 92c048860f..70671fa01d 100644 --- a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.cpp +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.cpp @@ -7,19 +7,19 @@ #include #include -#include +#include +#include -#include "Common/FileUtil.h" +#include "Common/JsonUtil.h" #include "Common/Logging/Log.h" -#include "Common/StringUtil.h" -#include "Common/VariantUtil.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" +#include "VideoCommon/GraphicsModEditor/Controls/AssetDisplay.h" +#include "VideoCommon/GraphicsModEditor/EditorEvents.h" +#include "VideoCommon/GraphicsModEditor/EditorMain.h" +#include "VideoCommon/GraphicsModSystem/Runtime/CustomResourceManager.h" std::unique_ptr CustomPipelineAction::Create(std::shared_ptr library) @@ -31,58 +31,17 @@ std::unique_ptr CustomPipelineAction::Create(const picojson::value& json_data, std::shared_ptr library) { - std::vector pipeline_passes; + const auto material_asset = + ReadStringFromJson(json_data.get(), "material_asset"); - const auto& passes_json = json_data.get("passes"); - if (passes_json.is()) + if (!material_asset) { - 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"); + ERROR_LOG_FMT(VIDEO, + "Failed to load custom pipeline action, 'material_asset' does not have a value"); 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)); + return std::make_unique(std::move(library), std::move(*material_asset)); } CustomPipelineAction::CustomPipelineAction(std::shared_ptr library) @@ -90,12 +49,10 @@ CustomPipelineAction::CustomPipelineAction(std::shared_ptr library, - std::vector pass_descriptions) - : m_library(std::move(library)), m_passes_config(std::move(pass_descriptions)) +CustomPipelineAction::CustomPipelineAction(std::shared_ptr library, + std::string material_asset) + : m_library(std::move(library)), m_material_asset(std::move(material_asset)) { - m_pipeline_passes.resize(m_passes_config.size()); } void CustomPipelineAction::OnDrawStarted(GraphicsModActionData::DrawStarted* draw_started) @@ -103,23 +60,49 @@ void CustomPipelineAction::OnDrawStarted(GraphicsModActionData::DrawStarted* dra if (!draw_started) [[unlikely]] return; - if (!draw_started->custom_pixel_shader) [[unlikely]] + if (!draw_started->material) [[unlikely]] return; - if (m_pipeline_passes.empty()) [[unlikely]] + if (m_material_asset.empty()) return; - auto& loader = Core::System::GetInstance().GetCustomAssetLoader(); - - // For now assume a single pass - const auto& pass_config = m_passes_config[0]; - auto& pass = m_pipeline_passes[0]; - - pass.UpdatePixelData(loader, m_library, draw_started->texture_units, - pass_config.m_pixel_material_asset); - CustomPixelShader custom_pixel_shader; - custom_pixel_shader.custom_shader = pass.m_last_generated_shader_code.GetBuffer(); - custom_pixel_shader.material_uniform_block = pass.m_last_generated_material_code.GetBuffer(); - *draw_started->custom_pixel_shader = custom_pixel_shader; - *draw_started->material_uniform_buffer = pass.m_material_data; + auto& resource_manager = Core::System::GetInstance().GetCustomResourceManager(); + *draw_started->material = resource_manager.GetMaterialFromAsset(m_material_asset, m_library, + draw_started->draw_data_view); +} + +void CustomPipelineAction::DrawImGui() +{ + auto& editor = Core::System::GetInstance().GetGraphicsModEditor(); + if (ImGui::CollapsingHeader("Custom pipeline", ImGuiTreeNodeFlags_DefaultOpen)) + { + if (ImGui::BeginTable("CustomPipelineForm", 2)) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Material"); + ImGui::TableNextColumn(); + if (GraphicsModEditor::Controls::AssetDisplay("CustomPipelineActionMaterial", + editor.GetEditorState(), &m_material_asset, + GraphicsModEditor::RasterMaterial)) + { + GraphicsModEditor::EditorEvents::AssetReloadEvent::Trigger(m_material_asset); + } + ImGui::EndTable(); + } + } +} + +void CustomPipelineAction::SerializeToConfig(picojson::object* obj) +{ + if (!obj) [[unlikely]] + return; + + auto& json_obj = *obj; + json_obj["material_asset"] = picojson::value{m_material_asset}; +} + +std::string CustomPipelineAction::GetFactoryName() const +{ + return "custom_pipeline"; } diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.h index 99026b99ce..e953aa18ec 100644 --- a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.h +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.h @@ -6,22 +6,15 @@ #include #include #include -#include #include #include "VideoCommon/Assets/CustomAssetLibrary.h" -#include "VideoCommon/GraphicsModSystem/Runtime/CustomPipeline.h" #include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h" class CustomPipelineAction final : public GraphicsModAction { public: - struct PipelinePassPassDescription - { - std::string m_pixel_material_asset; - }; - static constexpr std::string_view factory_name = "custom_pipeline"; static std::unique_ptr Create(const picojson::value& json_data, @@ -30,11 +23,14 @@ public: Create(std::shared_ptr library); explicit CustomPipelineAction(std::shared_ptr library); CustomPipelineAction(std::shared_ptr library, - std::vector pass_descriptions); + std::string material_asset); void OnDrawStarted(GraphicsModActionData::DrawStarted*) override; + void DrawImGui() override; + void SerializeToConfig(picojson::object* obj) override; + std::string GetFactoryName() const override; + private: std::shared_ptr m_library; - std::vector m_passes_config; - std::vector m_pipeline_passes; + std::string m_material_asset; }; diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/ModifyLight.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/ModifyLight.cpp new file mode 100644 index 0000000000..02b0d717da --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/ModifyLight.cpp @@ -0,0 +1,135 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModSystem/Runtime/Actions/ModifyLight.h" + +#include "VideoCommon/GraphicsModEditor/EditorEvents.h" + +#include + +std::unique_ptr ModifyLightAction::Create(const picojson::value& json_data) +{ + // TODO + return nullptr; +} + +std::unique_ptr ModifyLightAction::Create() +{ + return std::make_unique(float4{}, float4{}, float4{}, float4{}, float4{}); +} + +ModifyLightAction::ModifyLightAction(std::optional color, std::optional cosatt, + std::optional distatt, std::optional pos, + std::optional dir) + : m_color(color), m_cosatt(cosatt), m_distatt(distatt), m_pos(pos), m_dir(dir) +{ +} + +void ModifyLightAction::DrawImGui() +{ + float4 color = {}; + if (m_color) + color = *m_color; + if (ImGui::ColorEdit4("Color", color.data())) + { + GraphicsModEditor::EditorEvents::ChangeOccurredEvent::Trigger(); + m_color = color; + } + + float4 cosatt = {}; + if (m_cosatt) + cosatt = *m_cosatt; + if (ImGui::InputFloat4("Cosine Attenuation", cosatt.data())) + { + GraphicsModEditor::EditorEvents::ChangeOccurredEvent::Trigger(); + m_cosatt = cosatt; + } + + float4 distatt = {}; + if (m_distatt) + distatt = *m_distatt; + if (ImGui::InputFloat4("Distance Attenutation", distatt.data())) + { + GraphicsModEditor::EditorEvents::ChangeOccurredEvent::Trigger(); + m_distatt = distatt; + } + + float4 pos = {}; + if (m_pos) + pos = *m_pos; + if (ImGui::InputFloat4("Position", pos.data())) + { + GraphicsModEditor::EditorEvents::ChangeOccurredEvent::Trigger(); + m_pos = pos; + } + + float4 dir = {}; + if (m_dir) + dir = *m_dir; + if (ImGui::InputFloat4("Direction", dir.data())) + { + GraphicsModEditor::EditorEvents::ChangeOccurredEvent::Trigger(); + m_dir = dir; + } +} + +void ModifyLightAction::OnLight(GraphicsModActionData::Light* light) +{ + if (!light) [[unlikely]] + return; + + if (!light->color) [[unlikely]] + return; + + if (!light->cosatt) [[unlikely]] + return; + + if (!light->distatt) [[unlikely]] + return; + + if (!light->pos) [[unlikely]] + return; + + if (!light->dir) [[unlikely]] + return; + + if (m_color) + { + (*light->color)[0] = (*m_color)[0] * 255; + (*light->color)[1] = (*m_color)[1] * 255; + (*light->color)[2] = (*m_color)[2] * 255; + (*light->color)[3] = (*m_color)[3] * 255; + } + if (m_cosatt) + { + *light->cosatt = *m_cosatt; + } + if (m_distatt) + { + *light->distatt = *m_distatt; + } + if (m_pos) + { + *light->pos = *m_pos; + } + if (m_dir) + { + *light->dir = *m_dir; + } +} + +void ModifyLightAction::SerializeToConfig(picojson::object* obj) +{ + if (!obj) [[unlikely]] + return; + + // auto& json_obj = *obj; + /*json_obj["X"] = picojson::value{static_cast(m_position_offset.x)}; + json_obj["Y"] = picojson::value{static_cast(m_position_offset.y)}; + json_obj["Z"] = picojson::value{static_cast(m_position_offset.z)};*/ +} + +std::string ModifyLightAction::GetFactoryName() const +{ + return "modify_light"; +} diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/ModifyLight.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/ModifyLight.h new file mode 100644 index 0000000000..1b2078063d --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/ModifyLight.h @@ -0,0 +1,37 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include + +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h" + +class ModifyLightAction final : public GraphicsModAction +{ +public: + static std::unique_ptr Create(const picojson::value& json_data); + static std::unique_ptr Create(); + + ModifyLightAction() = default; + explicit ModifyLightAction(std::optional color, std::optional cosatt, + std::optional distatt, std::optional pos, + std::optional dir); + + void DrawImGui() override; + + void OnLight(GraphicsModActionData::Light* light_data) override; + + void SerializeToConfig(picojson::object* obj) override; + std::string GetFactoryName() const override; + +private: + std::optional m_color = {}; + std::optional m_cosatt = {}; + std::optional m_distatt = {}; + std::optional m_pos = {}; + std::optional m_dir = {}; +}; diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/MoveAction.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/MoveAction.cpp index d141af585c..4bda47b73e 100644 --- a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/MoveAction.cpp +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/MoveAction.cpp @@ -2,6 +2,9 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "VideoCommon/GraphicsModSystem/Runtime/Actions/MoveAction.h" +#include "VideoCommon/GraphicsModEditor/EditorEvents.h" + +#include std::unique_ptr MoveAction::Create(const picojson::value& json_data) { @@ -26,10 +29,31 @@ std::unique_ptr MoveAction::Create(const picojson::value& json_data) return std::make_unique(position_offset); } +std::unique_ptr MoveAction::Create() +{ + return std::make_unique(Common::Vec3{}); +} + MoveAction::MoveAction(Common::Vec3 position_offset) : m_position_offset(position_offset) { } +void MoveAction::DrawImGui() +{ + if (ImGui::InputFloat("X", &m_position_offset.x)) + { + GraphicsModEditor::EditorEvents::ChangeOccurredEvent::Trigger(); + } + if (ImGui::InputFloat("Y", &m_position_offset.y)) + { + GraphicsModEditor::EditorEvents::ChangeOccurredEvent::Trigger(); + } + if (ImGui::InputFloat("Z", &m_position_offset.z)) + { + GraphicsModEditor::EditorEvents::ChangeOccurredEvent::Trigger(); + } +} + void MoveAction::OnProjection(GraphicsModActionData::Projection* projection) { if (!projection) [[unlikely]] @@ -53,3 +77,19 @@ void MoveAction::OnProjectionAndTexture(GraphicsModActionData::Projection* proje auto& matrix = *projection->matrix; matrix = matrix * Common::Matrix44::Translate(m_position_offset); } + +void MoveAction::SerializeToConfig(picojson::object* obj) +{ + if (!obj) [[unlikely]] + return; + + auto& json_obj = *obj; + json_obj["X"] = picojson::value{static_cast(m_position_offset.x)}; + json_obj["Y"] = picojson::value{static_cast(m_position_offset.y)}; + json_obj["Z"] = picojson::value{static_cast(m_position_offset.z)}; +} + +std::string MoveAction::GetFactoryName() const +{ + return "move"; +} diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/MoveAction.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/MoveAction.h index b7a0358ce8..b6a3aa7a95 100644 --- a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/MoveAction.h +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/MoveAction.h @@ -15,10 +15,19 @@ class MoveAction final : public GraphicsModAction public: static constexpr std::string_view factory_name = "move"; static std::unique_ptr Create(const picojson::value& json_data); + static std::unique_ptr Create(); + + MoveAction() = default; explicit MoveAction(Common::Vec3 position_offset); + + void DrawImGui() override; + void OnProjection(GraphicsModActionData::Projection* projection) override; void OnProjectionAndTexture(GraphicsModActionData::Projection* projection) override; + void SerializeToConfig(picojson::object* obj) override; + std::string GetFactoryName() const override; + private: Common::Vec3 m_position_offset; }; diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/RelativeCameraAction.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/RelativeCameraAction.cpp new file mode 100644 index 0000000000..2fa98b1ce0 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/RelativeCameraAction.cpp @@ -0,0 +1,181 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModSystem/Runtime/Actions/RelativeCameraAction.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include "Common/JsonUtil.h" +#include "Core/System.h" + +#include "VideoCommon/AbstractTexture.h" +#include "VideoCommon/FreeLookCamera.h" +#include "VideoCommon/GraphicsModEditor/EditorBackend.h" +#include "VideoCommon/GraphicsModEditor/EditorEvents.h" +#include "VideoCommon/GraphicsModEditor/EditorMain.h" +#include "VideoCommon/GraphicsModEditor/EditorState.h" +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h" + +std::unique_ptr +RelativeCameraAction::Create(const picojson::value& json_data, + std::shared_ptr) +{ + if (!json_data.is()) + return nullptr; + + // const auto& obj = json_data.get(); + + return std::make_unique(); +} + +RelativeCameraAction::RelativeCameraAction(GraphicsModSystem::Camera camera) + : m_camera(std::move(camera)) +{ +} + +void RelativeCameraAction::OnDrawStarted(GraphicsModActionData::DrawStarted* draw_started) +{ + if (!draw_started) [[unlikely]] + return; + + if (!draw_started->camera) [[unlikely]] + return; + + *draw_started->camera = m_camera; +} + +void RelativeCameraAction::DrawImGui() +{ + if (ImGui::CollapsingHeader("Camera Data", ImGuiTreeNodeFlags_DefaultOpen)) + { + if (ImGui::BeginTable("Camera Form", 2)) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Transform"); + ImGui::TableNextColumn(); + for (std::size_t i = 0; i < 4; i++) + { + const std::span vec4 = m_camera.transform.data | std::views::take(4); + ImGui::InputFloat4(fmt::format("##CameraTransform{}", i).c_str(), vec4.data()); + } + if (ImGui::Button("Set to current Freelook view")) + { + m_camera.transform = g_freelook_camera.GetView(); + } + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Generate new render targets"); + ImGui::TableNextColumn(); + ImGui::Checkbox("##GenerateRenderTargets", &m_camera.generates_render_targets); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Resolution Multiplier"); + ImGui::TableNextColumn(); + if (!m_camera.generates_render_targets) + ImGui::BeginDisabled(); + if (ImGui::BeginCombo("##ResolutionMultiplier", + fmt::to_string(m_camera.resolution_multiplier).c_str())) + { + using ResolutionMultiplierValue = std::pair; + static constexpr std::array available = { + ResolutionMultiplierValue{"0.25", 0.25f}, ResolutionMultiplierValue{"0.5", 0.5f}, + ResolutionMultiplierValue{"1.0", 1.0f}, ResolutionMultiplierValue{"2.0", 2.0f}}; + for (const auto& option : available) + { + const bool is_selected = option.second == m_camera.resolution_multiplier; + if (ImGui::Selectable(fmt::format("{}##", option.first).c_str(), is_selected)) + { + m_camera.resolution_multiplier = option.second; + } + } + ImGui::EndCombo(); + } + if (!m_camera.generates_render_targets) + ImGui::EndDisabled(); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Render Target Type"); + ImGui::TableNextColumn(); + if (!m_camera.generates_render_targets) + ImGui::BeginDisabled(); + if (ImGui::BeginCombo("##RenderTargetType", + fmt::to_string(m_camera.render_target_type).c_str())) + { + static constexpr std::array all_texture_types = { + AbstractTextureType::Texture_2D, AbstractTextureType::Texture_CubeMap}; + + for (const auto& texture_type : all_texture_types) + { + const bool is_selected = texture_type == m_camera.render_target_type; + const auto texture_type_str = fmt::to_string(texture_type); + if (ImGui::Selectable(fmt::format("{}##", texture_type_str).c_str(), is_selected)) + { + m_camera.render_target_type = texture_type; + } + } + ImGui::EndCombo(); + } + if (!m_camera.generates_render_targets) + ImGui::EndDisabled(); + ImGui::EndTable(); + } + } + + if (m_camera.generates_render_targets && + ImGui::CollapsingHeader("Render Targets", ImGuiTreeNodeFlags_DefaultOpen)) + { + auto& manager = Core::System::GetInstance().GetGraphicsModManager(); + auto& backend = static_cast(manager.GetBackend()); + const auto render_targets = backend.GetCameraManager().GetRenderTargets(GetDrawCall()); + for (const auto& [name, render_target_texture] : render_targets) + { + // TODO: this is duplicated from property panel (minus copy hash), we should move this to a + // function + const float column_width = ImGui::GetContentRegionAvail().x; + float image_width = render_target_texture->GetWidth(); + float image_height = render_target_texture->GetHeight(); + const float image_aspect_ratio = image_width / image_height; + + const float final_width = std::min(image_width * 4, column_width); + image_width = final_width; + image_height = final_width / image_aspect_ratio; + + ImGui::ImageButton(name.data(), *render_target_texture, ImVec2{image_width, image_height}); + if (ImGui::BeginPopupContextItem()) + { + if (ImGui::Selectable("Dump")) + { + VideoCommon::TextureUtils::DumpTexture(*render_target_texture, std::string{name}, 0, + false); + } + ImGui::EndPopup(); + } + ImGui::Text("%s %dx%d", name.data(), render_target_texture->GetWidth(), + render_target_texture->GetHeight()); + } + } +} + +void RelativeCameraAction::SerializeToConfig(picojson::object* obj) +{ + if (!obj) [[unlikely]] + return; + + auto& json_obj = *obj; + json_obj["generates_render_targets"] = picojson::value{m_camera.generates_render_targets}; + json_obj["resolution_multiplier"] = + picojson::value{static_cast(m_camera.resolution_multiplier)}; +} + +std::string RelativeCameraAction::GetFactoryName() const +{ + return std::string{factory_name}; +} diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/RelativeCameraAction.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/RelativeCameraAction.h new file mode 100644 index 0000000000..bf392eaf2f --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/RelativeCameraAction.h @@ -0,0 +1,27 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h" + +class RelativeCameraAction final : public GraphicsModAction +{ +public: + static constexpr std::string_view factory_name = "relative_camera"; + static std::unique_ptr + Create(const picojson::value& json_data, + std::shared_ptr library); + explicit RelativeCameraAction(GraphicsModSystem::Camera camera); + RelativeCameraAction() = default; + void OnDrawStarted(GraphicsModActionData::DrawStarted*) override; + + void DrawImGui() override; + void SerializeToConfig(picojson::object* obj) override; + std::string GetFactoryName() const override; + +private: + GraphicsModSystem::Camera m_camera; +}; diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/ScaleAction.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/ScaleAction.cpp index 305cd8b737..1b11e1b281 100644 --- a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/ScaleAction.cpp +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/ScaleAction.cpp @@ -3,6 +3,10 @@ #include "VideoCommon/GraphicsModSystem/Runtime/Actions/ScaleAction.h" +#include "VideoCommon/GraphicsModEditor/EditorEvents.h" + +#include + std::unique_ptr ScaleAction::Create(const picojson::value& json_data) { Common::Vec3 scale; @@ -26,6 +30,11 @@ std::unique_ptr ScaleAction::Create(const picojson::value& json_dat return std::make_unique(scale); } +std::unique_ptr ScaleAction::Create() +{ + return std::make_unique(Common::Vec3{}); +} + ScaleAction::ScaleAction(Common::Vec3 scale) : m_scale(scale) { } @@ -73,3 +82,35 @@ void ScaleAction::OnProjectionAndTexture(GraphicsModActionData::Projection* proj matrix.data[0] = matrix.data[0] * m_scale.x; matrix.data[5] = matrix.data[5] * m_scale.y; } + +void ScaleAction::DrawImGui() +{ + if (ImGui::InputFloat("X", &m_scale.x)) + { + GraphicsModEditor::EditorEvents::ChangeOccurredEvent::Trigger(); + } + if (ImGui::InputFloat("Y", &m_scale.y)) + { + GraphicsModEditor::EditorEvents::ChangeOccurredEvent::Trigger(); + } + if (ImGui::InputFloat("Z", &m_scale.z)) + { + GraphicsModEditor::EditorEvents::ChangeOccurredEvent::Trigger(); + } +} + +void ScaleAction::SerializeToConfig(picojson::object* obj) +{ + if (!obj) [[unlikely]] + return; + + auto& json_obj = *obj; + json_obj["X"] = picojson::value{static_cast(m_scale.x)}; + json_obj["Y"] = picojson::value{static_cast(m_scale.y)}; + json_obj["Z"] = picojson::value{static_cast(m_scale.z)}; +} + +std::string ScaleAction::GetFactoryName() const +{ + return "scale"; +} diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/ScaleAction.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/ScaleAction.h index 4673ed2d18..3936d6d952 100644 --- a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/ScaleAction.h +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/ScaleAction.h @@ -15,11 +15,18 @@ class ScaleAction final : public GraphicsModAction public: static constexpr std::string_view factory_name = "scale"; static std::unique_ptr Create(const picojson::value& json_data); + static std::unique_ptr Create(); + explicit ScaleAction(Common::Vec3 scale); void OnEFB(GraphicsModActionData::EFB*) override; void OnProjection(GraphicsModActionData::Projection*) override; void OnProjectionAndTexture(GraphicsModActionData::Projection*) override; + void DrawImGui() override; + + void SerializeToConfig(picojson::object* obj) override; + std::string GetFactoryName() const override; + private: Common::Vec3 m_scale; }; diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/SkipAction.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/SkipAction.cpp index b693fef4f7..165dca4193 100644 --- a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/SkipAction.cpp +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/SkipAction.cpp @@ -24,3 +24,19 @@ void SkipAction::OnEFB(GraphicsModActionData::EFB* efb) *efb->skip = true; } + +void SkipAction::OnLight(GraphicsModActionData::Light* light) +{ + if (!light) [[unlikely]] + return; + + if (!light->skip) [[unlikely]] + return; + + *light->skip = true; +} + +std::string SkipAction::GetFactoryName() const +{ + return "skip"; +} diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/SkipAction.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/SkipAction.h index 8cda643c5a..aefb57695f 100644 --- a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/SkipAction.h +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/SkipAction.h @@ -11,4 +11,6 @@ public: static constexpr std::string_view factory_name = "skip"; void OnDrawStarted(GraphicsModActionData::DrawStarted*) override; void OnEFB(GraphicsModActionData::EFB*) override; + void OnLight(GraphicsModActionData::Light*) override; + std::string GetFactoryName() const override; }; diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/TransformAction.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/TransformAction.cpp new file mode 100644 index 0000000000..1d4eef8533 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/TransformAction.cpp @@ -0,0 +1,132 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModSystem/Runtime/Actions/TransformAction.h" + +#include "Common/JsonUtil.h" + +#include "VideoCommon/GraphicsModEditor/EditorEvents.h" + +#include + +std::unique_ptr TransformAction::Create(const picojson::value& json_data) +{ + if (!json_data.is()) + return nullptr; + + const auto& obj = json_data.get(); + + Common::Vec3 scale{1, 1, 1}; + if (const auto it = obj.find("scale"); it != obj.end()) + { + if (it->second.is()) + { + FromJson(it->second.get(), scale); + } + } + + Common::Vec3 translation{}; + if (const auto it = obj.find("translation"); it != obj.end()) + { + if (it->second.is()) + { + FromJson(it->second.get(), translation); + } + } + + Common::Vec3 rotation{}; + if (const auto it = obj.find("rotation"); it != obj.end()) + { + if (it->second.is()) + { + FromJson(it->second.get(), rotation); + } + } + return std::make_unique(std::move(rotation), std::move(scale), + std::move(translation)); +} + +std::unique_ptr TransformAction::Create() +{ + return std::make_unique(Common::Vec3{}, Common::Vec3{1, 1, 1}, Common::Vec3{}); +} + +TransformAction::TransformAction(Common::Vec3 rotation, Common::Vec3 scale, + Common::Vec3 translation) + : m_rotation(std::move(rotation)), m_scale(std::move(scale)), + m_translation(std::move(translation)), m_calculated_transform(Common::Matrix44::Identity()), + m_transform_changed(true) +{ +} + +void TransformAction::DrawImGui() +{ + if (ImGui::CollapsingHeader("Transform", ImGuiTreeNodeFlags_DefaultOpen)) + { + if (ImGui::BeginTable("TransformTable", 2)) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Scale"); + ImGui::TableNextColumn(); + if (ImGui::InputFloat3("##Scale", m_scale.data.data())) + { + GraphicsModEditor::EditorEvents::ChangeOccurredEvent::Trigger(); + m_transform_changed = true; + } + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Rotation"); + ImGui::TableNextColumn(); + if (ImGui::InputFloat3("##Rotation", m_rotation.data.data())) + { + GraphicsModEditor::EditorEvents::ChangeOccurredEvent::Trigger(); + m_transform_changed = true; + } + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Translate"); + ImGui::TableNextColumn(); + if (ImGui::InputFloat3("##Translate", m_translation.data.data())) + { + GraphicsModEditor::EditorEvents::ChangeOccurredEvent::Trigger(); + m_transform_changed = true; + } + ImGui::EndTable(); + } + } +} + +void TransformAction::OnDrawStarted(GraphicsModActionData::DrawStarted* draw_started) +{ + if (!draw_started) [[unlikely]] + return; + + if (m_transform_changed) + { + const auto scale = Common::Matrix33::Scale(m_scale); + const auto rotation = Common::Quaternion::RotateXYZ(m_rotation); + m_calculated_transform = Common::Matrix44::Translate(m_translation) * + Common::Matrix44::FromQuaternion(rotation) * + Common::Matrix44::FromMatrix33(scale); + + m_transform_changed = false; + } + *draw_started->transform = m_calculated_transform; +} + +void TransformAction::SerializeToConfig(picojson::object* obj) +{ + if (!obj) [[unlikely]] + return; + + auto& json_obj = *obj; + json_obj.emplace("translation", ToJsonObject(m_translation)); + json_obj.emplace("scale", ToJsonObject(m_scale)); + json_obj.emplace("rotation", ToJsonObject(m_rotation)); +} + +std::string TransformAction::GetFactoryName() const +{ + return "transform"; +} diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/TransformAction.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/TransformAction.h new file mode 100644 index 0000000000..4698eeb385 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/TransformAction.h @@ -0,0 +1,37 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include + +#include "Common/Matrix.h" +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h" + +class TransformAction final : public GraphicsModAction +{ +public: + static constexpr std::string_view factory_name = "transform"; + static std::unique_ptr Create(const picojson::value& json_data); + static std::unique_ptr Create(); + + TransformAction() = default; + TransformAction(Common::Vec3 rotation, Common::Vec3 scale, Common::Vec3 translation); + + void OnDrawStarted(GraphicsModActionData::DrawStarted*) override; + + void DrawImGui() override; + + void SerializeToConfig(picojson::object* obj) override; + std::string GetFactoryName() const override; + +private: + Common::Vec3 m_rotation = Common::Vec3{0, 0, 0}; + Common::Vec3 m_scale = Common::Vec3{1, 1, 1}; + Common::Vec3 m_translation = Common::Vec3{0, 0, 0}; + Common::Matrix44 m_calculated_transform = Common::Matrix44::Identity(); + bool m_transform_changed = false; +}; diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CameraManager.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CameraManager.cpp new file mode 100644 index 0000000000..2b86cee0ab --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CameraManager.cpp @@ -0,0 +1,311 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModSystem/Runtime/CameraManager.h" + +#include + +#include +#include + +#include "Common/EnumUtils.h" + +#include "VideoCommon/AbstractGfx.h" +#include "VideoCommon/FramebufferManager.h" +#include "VideoCommon/VideoConfig.h" + +namespace +{ +constexpr u64 CAMERA_FRAME_LIVE_TIME = 5; +} + +namespace VideoCommon +{ +void CameraManager::EnsureCamera(GraphicsModSystem::DrawCallID originating_draw_call_id, + GraphicsModSystem::Camera camera) +{ + auto& internal_camera = m_all_cameras[originating_draw_call_id]; + internal_camera.camera = std::move(camera); + + // Note: invalid denotes the default Dolphin camera + internal_camera.id = GraphicsModSystem::DrawCallID::INVALID; + + const bool inactive = internal_camera.frame == 0; + internal_camera.frame = m_last_frame; + + if (internal_camera.camera.generates_render_targets) + { + bool camera_changed = false; + if (m_current_camera == &internal_camera) + { + // Camera changed by user? + m_current_camera = nullptr; + camera_changed = true; + } + + internal_camera.id = originating_draw_call_id; + if (inactive || camera_changed) + { + m_additional_active_cameras.push_back(&internal_camera); + } + } + else + { + m_current_camera = &internal_camera; + } +} + +AbstractTexture* +CameraManager::GetRenderTarget(GraphicsModSystem::DrawCallID originating_draw_call_id, + std::string_view render_target_name) const +{ + // If we have the default camera, we use special names for the color/depth buffer + // managed by Dolphin + if (originating_draw_call_id == GraphicsModSystem::DrawCallID::INVALID) + { + if (render_target_name == "color") + { + return g_gfx->GetCurrentFramebuffer()->GetColorAttachment(); + } + else if (render_target_name == "depth") + { + return g_gfx->GetCurrentFramebuffer()->GetDepthAttachment(); + } + } + + if (const auto iter = m_camera_draw_call_to_render_target.find(originating_draw_call_id); + iter != m_camera_draw_call_to_render_target.end()) + { + if (const auto render_iter = iter->second.find(render_target_name); + render_iter != iter->second.end()) + { + return render_iter->second; + } + } + + return nullptr; +} + +CameraManager::RenderTargetMap +CameraManager::GetRenderTargets(GraphicsModSystem::DrawCallID originating_draw_call_id) const +{ + if (const auto iter = m_camera_draw_call_to_render_target.find(originating_draw_call_id); + iter != m_camera_draw_call_to_render_target.end()) + { + return iter->second; + } + + return {}; +} + +CameraManager::CameraView CameraManager::GetCurrentCameraView( + std::span render_targets) +{ + if (render_targets.empty()) + { + const GraphicsModSystem::DrawCallID draw_call_id = + m_current_camera ? m_current_camera->id : GraphicsModSystem::DrawCallID::INVALID; + const auto transform = + m_current_camera ? std::make_optional<>(m_current_camera->camera.transform) : std::nullopt; + return {draw_call_id, nullptr, transform}; + } + return GetViewFromCamera(m_current_camera, render_targets); +} + +std::vector CameraManager::GetAdditionalViews( + std::span render_targets) +{ + std::vector result; + for (const auto& camera : m_additional_active_cameras) + { + result.push_back(GetViewFromCamera(camera, render_targets)); + } + return result; +} + +std::vector CameraManager::GetDrawCallsWithCameras() const +{ + std::vector draw_calls; + for (const auto& [draw_call, camera] : m_all_cameras) + { + draw_calls.push_back(draw_call); + } + + return draw_calls; +} + +void CameraManager::ClearCameraRenderTargets(const MathUtil::Rectangle& rc, bool color_enable, + bool alpha_enable, bool z_enable, u32 color, u32 z) +{ + /*const auto frame_buffer = g_gfx->GetCurrentFramebuffer(); + for (const auto& [id, target_frame_buffer] : m_render_target_camera_hash_to_framebuffers) + { + g_gfx->SetFramebuffer(target_frame_buffer.get()); + // Native -> EFB coordinates + MathUtil::Rectangle target_rc = g_framebuffer_manager->ConvertEFBRectangle(rc); + target_rc = g_gfx->ConvertFramebufferRectangle(target_rc, target_frame_buffer.get()); + target_rc.ClampUL(0, 0, target_frame_buffer->GetWidth(), target_frame_buffer->GetWidth()); + + // Determine whether the EFB has an alpha channel. If it doesn't, we can clear the alpha + // channel to 0xFF. + // On backends that don't allow masking Alpha clears, this allows us to use the fast path + // almost all the time + if (bpmem.zcontrol.pixel_format == PixelFormat::RGB565_Z16 || + bpmem.zcontrol.pixel_format == PixelFormat::RGB8_Z24 || + bpmem.zcontrol.pixel_format == PixelFormat::Z24) + { + // Force alpha writes, and clear the alpha channel. + alpha_enable = true; + color &= 0x00FFFFFF; + } + + g_gfx->ClearRegion(target_rc, color_enable, alpha_enable, z_enable, color, z); + //g_gfx->SetAndClearFramebuffer(target_frame_buffer.get(), {{0.0f, 0.0f, 0.0f, 1.0f}}, + // g_backend_info.bSupportsReversedDepthRange ? 1.0f : + // 0.0f); + } + g_gfx->SetFramebuffer(frame_buffer);*/ + + const auto frame_buffer = g_gfx->GetCurrentFramebuffer(); + for (const auto& [id, target_frame_buffer] : m_render_target_camera_hash_to_framebuffers) + { + g_gfx->SetAndClearFramebuffer(target_frame_buffer.get(), {{0.0f, 0.0f, 0.0f, 1.0f}}, + g_backend_info.bSupportsReversedDepthRange ? 1.0f : 0.0f); + } + g_gfx->SetFramebuffer(frame_buffer); +} + +void CameraManager::FrameUpdate(u64 frame) +{ + std::vector ids_to_return_to_pool; + + // Can the current camera be reset? + if (m_current_camera && (m_current_camera->frame + CAMERA_FRAME_LIVE_TIME) < m_last_frame) + { + m_current_camera->frame = 0; + ids_to_return_to_pool.push_back(GraphicsModSystem::DrawCallID::INVALID); + m_current_camera = nullptr; + } + + // Find additional cameras that can be reset + for (const auto& camera : m_additional_active_cameras) + { + if (camera->frame != m_last_frame) + { + ids_to_return_to_pool.push_back(camera->id); + camera->frame = 0; + } + } + // Now we can erase them + std::erase_if(m_additional_active_cameras, [&](InternalCamera* camera) { + return (camera->frame + CAMERA_FRAME_LIVE_TIME) < m_last_frame; + }); + + // Return any render targets to the pool + /*for (const auto id : ids_to_return_to_pool) + { + auto& render_target_map = m_camera_draw_call_to_render_target[id]; + for (const auto& [name, texture] : render_target_map) + { + const auto full_name = fmt::format("{}_{}", Common::ToUnderlying(id), name); + m_texture_cache.ReleaseToPool(full_name); + } + m_camera_draw_call_to_render_target.erase(id); + }*/ + + const auto frame_buffer = g_gfx->GetCurrentFramebuffer(); + for (const auto& [id, target_frame_buffer] : m_render_target_camera_hash_to_framebuffers) + { + g_gfx->SetAndClearFramebuffer(target_frame_buffer.get(), {{0.0f, 0.0f, 0.0f, 1.0f}}, + g_backend_info.bSupportsReversedDepthRange ? 1.0f : 0.0f); + } + g_gfx->SetFramebuffer(frame_buffer); + + m_last_frame = frame; +} + +AbstractTexture* CameraManager::EnsureRenderTarget( + InternalCamera* camera, const GraphicsModSystem::OutputRenderTargetResource& render_target) +{ + const auto id = camera ? camera->id : GraphicsModSystem::DrawCallID::INVALID; + auto& render_target_map = m_camera_draw_call_to_render_target[id]; + const auto [it, added] = render_target_map.try_emplace(render_target.name, nullptr); + if (added) + { + const float resolution_multiplier = camera ? camera->camera.resolution_multiplier : 1.0; + const u32 width = g_framebuffer_manager->GetEFBWidth() * resolution_multiplier; + const u32 height = g_framebuffer_manager->GetEFBHeight() * resolution_multiplier; + const TextureConfig texture_config( + width, height, 1, 1, 1, render_target.format, AbstractTextureFlag_RenderTarget, + camera ? camera->camera.render_target_type : AbstractTextureType::Texture_2D); + + // If we have the default camera, we use special names for the color/depth buffer + // managed by Dolphin + if (id == GraphicsModSystem::DrawCallID::INVALID) + { + if (render_target.name == "color") + { + it->second = g_gfx->GetCurrentFramebuffer()->GetColorAttachment(); + return it->second; + } + else if (render_target.name == "depth") + { + it->second = g_gfx->GetCurrentFramebuffer()->GetDepthAttachment(); + return it->second; + } + } + + // Otherwise it's a custom render target, we must create it + const auto full_name = fmt::format("{}_{}", Common::ToUnderlying(id), render_target.name); + if (const auto texture = m_texture_cache.GetTextureFromConfig(full_name, texture_config)) + { + it->second = *texture; + } + } + + return it->second; +} + +CameraManager::CameraView CameraManager::GetViewFromCamera( + InternalCamera* camera, std::span render_targets) +{ + XXH3_state_t view_state; + XXH3_INITSTATE(&view_state); + XXH3_64bits_reset_withSeed(&view_state, static_cast(1)); + + if (camera) + { + XXH3_64bits_update(&view_state, &camera->id, sizeof(GraphicsModSystem::DrawCallID)); + XXH3_64bits_update(&view_state, &camera->camera, sizeof(GraphicsModSystem::Camera)); + } + for (const auto& render_target : render_targets) + { + XXH3_64bits_update(&view_state, render_target.name.data(), render_target.name.size()); + XXH3_64bits_update(&view_state, &render_target.format, sizeof(render_target.format)); + } + + const auto [it, added] = m_render_target_camera_hash_to_framebuffers.try_emplace( + XXH3_64bits_digest(&view_state), nullptr); + if (added) + { + static const GraphicsModSystem::OutputRenderTargetResource color_target_resource{ + .format = g_framebuffer_manager->GetEFBColorFormat(), .name = "color"}; + static const GraphicsModSystem::OutputRenderTargetResource depth_target_resource{ + .format = g_framebuffer_manager->GetEFBDepthFormat(), .name = "depth"}; + const auto color_target = EnsureRenderTarget(camera, color_target_resource); + const auto depth_target = EnsureRenderTarget(camera, depth_target_resource); + std::vector additional_render_targets; + for (const auto& render_target : render_targets) + { + additional_render_targets.push_back(EnsureRenderTarget(camera, render_target)); + } + it->second = + g_gfx->CreateFramebuffer(color_target, depth_target, std::move(additional_render_targets)); + } + + const GraphicsModSystem::DrawCallID draw_call_id = + camera ? camera->id : GraphicsModSystem::DrawCallID::INVALID; + const auto transform = camera ? std::make_optional<>(camera->camera.transform) : std::nullopt; + return {draw_call_id, it->second.get(), transform}; +} +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CameraManager.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CameraManager.h new file mode 100644 index 0000000000..4899cb285b --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CameraManager.h @@ -0,0 +1,77 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "VideoCommon/AbstractFramebuffer.h" +#include "VideoCommon/GraphicsModSystem/Runtime/CustomTextureCache2.h" +#include "VideoCommon/GraphicsModSystem/Types.h" + +namespace VideoCommon +{ +class CameraManager +{ +public: + void EnsureCamera(GraphicsModSystem::DrawCallID originating_draw_call_id, + GraphicsModSystem::Camera camera); + AbstractTexture* GetRenderTarget(GraphicsModSystem::DrawCallID originating_draw_call_id, + std::string_view render_target_name) const; + + using RenderTargetMap = std::map; + RenderTargetMap GetRenderTargets(GraphicsModSystem::DrawCallID originating_draw_call_id) const; + + struct CameraView + { + GraphicsModSystem::DrawCallID id; + AbstractFramebuffer* framebuffer; + std::optional transform; + bool skip_orthographic = true; + }; + CameraView + GetCurrentCameraView(std::span render_targets); + + std::vector + GetAdditionalViews(std::span render_targets); + + std::vector GetDrawCallsWithCameras() const; + + void ClearCameraRenderTargets(const MathUtil::Rectangle& rc, bool color_enable, + bool alpha_enable, bool z_enable, u32 color, u32 z); + + void FrameUpdate(u64 frame); + +private: + struct InternalCamera + { + GraphicsModSystem::Camera camera; + GraphicsModSystem::DrawCallID id; + u64 frame = 0; + }; + + AbstractTexture* + EnsureRenderTarget(InternalCamera* camera, + const GraphicsModSystem::OutputRenderTargetResource& render_target); + + CameraView + GetViewFromCamera(InternalCamera* camera, + std::span render_targets); + + std::map> m_render_target_camera_hash_to_framebuffers; + std::map m_camera_draw_call_to_render_target; + + CustomTextureCache2 m_texture_cache; + + std::map m_all_cameras; + std::vector m_additional_active_cameras; + InternalCamera* m_current_camera = nullptr; + + u64 m_last_frame = 0; +}; +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomPipeline.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomPipeline.cpp deleted file mode 100644 index 0001a9081d..0000000000 --- a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomPipeline.cpp +++ /dev/null @@ -1,411 +0,0 @@ -// Copyright 2024 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "VideoCommon/GraphicsModSystem/Runtime/CustomPipeline.h" - -#include -#include -#include - -#include "Common/Contains.h" -#include "Common/Logging/Log.h" -#include "Common/VariantUtil.h" - -#include "VideoCommon/AbstractGfx.h" -#include "VideoCommon/Assets/CustomAssetLoader.h" - -namespace -{ -bool IsQualifier(std::string_view value) -{ - static constexpr std::array qualifiers = { - "attribute", "const", "highp", "lowp", "mediump", "uniform", "varying", - }; - return Common::Contains(qualifiers, value); -} - -bool IsBuiltInMacro(std::string_view value) -{ - static constexpr std::array built_in = { - "__LINE__", "__FILE__", "__VERSION__", "GL_core_profile", "GL_compatibility_profile", - }; - return Common::Contains(built_in, value); -} - -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.emplace_back(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.emplace_back(last_identifier); - } - else if (source[i] == '=') - { - global_result.emplace_back(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; -} - -} // namespace - -void CustomPipeline::UpdatePixelData( - VideoCommon::CustomAssetLoader& loader, - std::shared_ptr library, std::span texture_units, - const VideoCommon::CustomAssetLibrary::AssetID& material_to_load) -{ - if (!m_pixel_material.m_asset || material_to_load != m_pixel_material.m_asset->GetAssetId()) - { - m_pixel_material.m_asset = loader.LoadMaterial(material_to_load, library); - } - - const auto material_data = m_pixel_material.m_asset->GetData(); - if (!material_data) - { - return; - } - - std::size_t max_material_data_size = 0; - if (m_pixel_material.m_asset->GetLastLoadedTime() > m_pixel_material.m_cached_write_time) - { - m_last_generated_material_code = ShaderCode{}; - m_pixel_material.m_cached_write_time = m_pixel_material.m_asset->GetLastLoadedTime(); - std::size_t texture_count = 0; - for (const auto& property : material_data->properties) - { - max_material_data_size += VideoCommon::MaterialProperty::GetMemorySize(property); - VideoCommon::MaterialProperty::WriteAsShaderCode(m_last_generated_material_code, property); - if (std::holds_alternative(property.m_value)) - { - texture_count++; - } - } - m_material_data.resize(max_material_data_size); - m_game_textures.resize(texture_count); - } - - if (!m_pixel_shader.m_asset || - m_pixel_shader.m_asset->GetLastLoadedTime() > m_pixel_shader.m_cached_write_time || - material_data->shader_asset != m_pixel_shader.m_asset->GetAssetId()) - { - m_pixel_shader.m_asset = loader.LoadPixelShader(material_data->shader_asset, library); - m_pixel_shader.m_cached_write_time = m_pixel_shader.m_asset->GetLastLoadedTime(); - - m_last_generated_shader_code = ShaderCode{}; - } - - const auto shader_data = m_pixel_shader.m_asset->GetData(); - if (!shader_data) - { - return; - } - - if (shader_data->m_properties.size() != material_data->properties.size()) - { - return; - } - - u8* material_buffer = m_material_data.data(); - u32 sampler_index = 8; - for (std::size_t index = 0; index < material_data->properties.size(); index++) - { - auto& property = material_data->properties[index]; - const auto shader_it = shader_data->m_properties.find(property.m_code_name); - if (shader_it == shader_data->m_properties.end()) - { - ERROR_LOG_FMT(VIDEO, - "Custom pipeline, has material asset '{}' that uses a " - "code name of '{}' but that can't be found on shader asset '{}'!", - m_pixel_material.m_asset->GetAssetId(), property.m_code_name, - m_pixel_shader.m_asset->GetAssetId()); - return; - } - - if (auto* texture_asset_id = - std::get_if(&property.m_value)) - { - if (*texture_asset_id != "") - { - auto asset = loader.LoadGameTexture(*texture_asset_id, library); - if (!asset) - { - return; - } - - auto& texture_asset = m_game_textures[index]; - if (!texture_asset || - texture_asset->m_cached_asset.m_asset->GetLastLoadedTime() > - texture_asset->m_cached_asset.m_cached_write_time || - *texture_asset_id != texture_asset->m_cached_asset.m_asset->GetAssetId()) - { - if (!texture_asset) - { - texture_asset = CachedTextureAsset{}; - } - const auto loaded_time = asset->GetLastLoadedTime(); - texture_asset->m_cached_asset = VideoCommon::CachedAsset{ - std::move(asset), loaded_time}; - texture_asset->m_texture.reset(); - - if (std::holds_alternative( - shader_it->second.m_default)) - { - texture_asset->m_sampler_code = - fmt::format("SAMPLER_BINDING({}) uniform sampler2D samp_{};\n", sampler_index, - property.m_code_name); - texture_asset->m_define_code = fmt::format("#define HAS_{} 1\n", property.m_code_name); - } - else if (std::holds_alternative( - shader_it->second.m_default)) - { - texture_asset->m_sampler_code = - fmt::format("SAMPLER_BINDING({}) uniform sampler2DArray samp_{};\n", sampler_index, - property.m_code_name); - texture_asset->m_define_code = fmt::format("#define HAS_{} 1\n", property.m_code_name); - } - else if (std::holds_alternative( - shader_it->second.m_default)) - { - texture_asset->m_sampler_code = - fmt::format("SAMPLER_BINDING({}) uniform samplerCube samp_{};\n", sampler_index, - property.m_code_name); - texture_asset->m_define_code = fmt::format("#define HAS_{} 1\n", property.m_code_name); - } - } - - const auto texture_data = texture_asset->m_cached_asset.m_asset->GetData(); - if (!texture_data) - { - return; - } - - if (texture_asset->m_texture) - { - g_gfx->SetTexture(sampler_index, texture_asset->m_texture.get()); - g_gfx->SetSamplerState(sampler_index, texture_data->m_sampler); - } - else - { - AbstractTextureType texture_type = AbstractTextureType::Texture_2DArray; - if (std::holds_alternative( - shader_it->second.m_default)) - { - texture_type = AbstractTextureType::Texture_CubeMap; - } - else if (std::holds_alternative( - shader_it->second.m_default)) - { - texture_type = AbstractTextureType::Texture_2D; - } - - if (texture_data->m_texture.m_slices.empty() || - texture_data->m_texture.m_slices[0].m_levels.empty()) - { - return; - } - - auto& first_slice = texture_data->m_texture.m_slices[0]; - const TextureConfig texture_config( - first_slice.m_levels[0].width, first_slice.m_levels[0].height, - static_cast(first_slice.m_levels.size()), - static_cast(texture_data->m_texture.m_slices.size()), 1, - first_slice.m_levels[0].format, 0, texture_type); - texture_asset->m_texture = g_gfx->CreateTexture( - texture_config, fmt::format("Custom shader texture '{}'", property.m_code_name)); - if (texture_asset->m_texture) - { - for (std::size_t slice_index = 0; slice_index < texture_data->m_texture.m_slices.size(); - slice_index++) - { - auto& slice = texture_data->m_texture.m_slices[slice_index]; - for (u32 level_index = 0; level_index < static_cast(slice.m_levels.size()); - ++level_index) - { - auto& level = slice.m_levels[level_index]; - texture_asset->m_texture->Load(level_index, level.width, level.height, - level.row_length, level.data.data(), - level.data.size(), static_cast(slice_index)); - } - } - } - } - - sampler_index++; - } - } - else - { - VideoCommon::MaterialProperty::WriteToMemory(material_buffer, property); - } - } - - if (m_last_generated_shader_code.GetBuffer().empty()) - { - // 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& game_texture : m_game_textures) - { - if (!game_texture) - continue; - - m_last_generated_shader_code.Write("{}", game_texture->m_sampler_code); - m_last_generated_shader_code.Write("{}", game_texture->m_define_code); - } - - for (std::size_t i = 0; i < texture_units.size(); i++) - { - const auto& texture_unit = texture_units[i]; - m_last_generated_shader_code.Write( - "#define TEX_COORD{} data.texcoord[data.texmap_to_texcoord_index[{}]].xy\n", i, - texture_unit); - } - m_last_generated_shader_code.Write("{}", color_shader_data); - } -} diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomPipeline.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomPipeline.h deleted file mode 100644 index 83bc9f84e1..0000000000 --- a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomPipeline.h +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2024 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#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/ShaderGenCommon.h" - -namespace VideoCommon -{ -class CustomAssetLoader; -} - -struct CustomPipeline -{ - void UpdatePixelData(VideoCommon::CustomAssetLoader& loader, - std::shared_ptr library, - std::span texture_units, - const VideoCommon::CustomAssetLibrary::AssetID& material_to_load); - - VideoCommon::CachedAsset m_pixel_material; - VideoCommon::CachedAsset m_pixel_shader; - - struct CachedTextureAsset - { - VideoCommon::CachedAsset m_cached_asset; - std::unique_ptr m_texture; - std::string m_sampler_code; - std::string m_define_code; - }; - std::vector> m_game_textures; - - ShaderCode m_last_generated_shader_code; - ShaderCode m_last_generated_material_code; - - std::vector m_material_data; -}; diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomResourceManager.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomResourceManager.cpp new file mode 100644 index 0000000000..779dbbb9b2 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomResourceManager.cpp @@ -0,0 +1,1021 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModSystem/Runtime/CustomResourceManager.h" + +#include +#include + +#include "Common/MathUtil.h" +#include "Common/MemoryUtil.h" +#include "Common/VariantUtil.h" + +#include "VideoCommon/AbstractGfx.h" +#include "VideoCommon/Assets/CustomAsset.h" +#include "VideoCommon/Assets/MaterialAsset.h" +#include "VideoCommon/Assets/MeshAsset.h" +#include "VideoCommon/Assets/RenderTargetAsset.h" +#include "VideoCommon/Assets/ShaderAsset.h" +#include "VideoCommon/Assets/TextureAsset.h" +#include "VideoCommon/BPMemory.h" +#include "VideoCommon/Present.h" +#include "VideoCommon/VideoConfig.h" +#include "VideoCommon/VideoEvents.h" + +namespace VideoCommon +{ +namespace +{ +u64 GetTextureUsageHash(std::span sampler_values, + std::span samplers) +{ + XXH3_state_t texture_usage_hash_state; + XXH3_INITSTATE(&texture_usage_hash_state); + XXH3_64bits_reset_withSeed(&texture_usage_hash_state, static_cast(1)); + + for (std::size_t i = 0; i < samplers.size(); i++) + { + const auto& sampler_value = sampler_values[i]; + const auto& sampler = samplers[i]; + + u8 type_converted = 0; + if (sampler_value.asset != "") + type_converted = static_cast(sampler.type) + 1; + XXH3_64bits_update(&texture_usage_hash_state, &type_converted, sizeof(type_converted)); + } + return XXH3_64bits_digest(&texture_usage_hash_state); +} + +SamplerState CalculateSamplerAnsiotropy(const SamplerState& initial_sampler) +{ + SamplerState state = initial_sampler; + if (g_ActiveConfig.iMaxAnisotropy != 0 && + !(state.tm0.min_filter == FilterMode::Near && state.tm0.mag_filter == FilterMode::Near)) + { + state.tm0.min_filter = FilterMode::Linear; + state.tm0.mag_filter = FilterMode::Linear; + state.tm0.anisotropic_filtering = true; + } + else + { + state.tm0.anisotropic_filtering = false; + } + return state; +} +} // namespace +void CustomResourceManager::Initialize() +{ + m_asset_loader.Initialize(); + + const size_t sys_mem = Common::MemPhysical(); + const size_t recommended_min_mem = 2 * size_t(1024 * 1024 * 1024); + // keep 2GB memory for system stability if system RAM is 4GB+ - use half of memory in other cases + m_max_ram_available = + (sys_mem / 2 < recommended_min_mem) ? (sys_mem / 2) : (sys_mem - recommended_min_mem); + + m_custom_shader_cache.Initialize(); +} + +void CustomResourceManager::Shutdown() +{ + Reset(); + + m_asset_loader.Shutdown(); + m_custom_shader_cache.Shutdown(); + m_custom_texture_cache.Reset(); +} + +void CustomResourceManager::Reset() +{ + m_asset_loader.Reset(true); + m_custom_shader_cache.Reload(); + + m_loaded_assets = {}; + m_pending_assets = {}; + m_session_id_to_asset_data.clear(); + m_asset_id_to_session_id.clear(); + m_ram_used = 0; + m_material_asset_cache.clear(); + m_material_name_cache.clear(); + + m_shader_asset_cache.clear(); + m_texture_asset_cache.clear(); + m_texture_data_asset_cache.clear(); + + m_pending_removals.clear(); +} + +void CustomResourceManager::SetHostConfig(const ShaderHostConfig& config) +{ + m_custom_shader_cache.SetHostConfig(config); + m_custom_shader_cache.Reload(); +} + +void CustomResourceManager::ReloadAsset(const CustomAssetLibrary::AssetID& asset_id) +{ + std::lock_guard guard(m_reload_mutex); + m_assets_to_reload.insert(asset_id); +} + +GraphicsModSystem::MaterialResource* CustomResourceManager::GetMaterialFromAsset( + const CustomAssetLibrary::AssetID& asset_id, + std::shared_ptr library, + GraphicsModSystem::DrawDataView draw_data) +{ + const auto [it, inserted] = + m_material_asset_cache.try_emplace(asset_id, InternalMaterialResource{}); + if (it->second.asset_data) + { + if (it->second.asset_data->load_type == AssetData::LoadType::LoadFinalyzed) + { + if (it->second.asset_data->has_errors) + { + // TODO: report to the system what the error is + return nullptr; + } + + const auto [material_for_uid_iter, added] = it->second.material_per_uid.try_emplace( + *draw_data.uid, GraphicsModSystem::MaterialResource{}); + + // TODO: + // if (added) + { + CreateTextureResources(draw_data, it->second, &material_for_uid_iter->second); + CreateRenderTargetResources(draw_data, it->second, &material_for_uid_iter->second); + if (!SetMaterialPipeline(draw_data, it->second, &material_for_uid_iter->second)) + return nullptr; + material_for_uid_iter->second.pixel_uniform_data = it->second.pixel_data; + material_for_uid_iter->second.vertex_uniform_data = it->second.vertex_data; + } + m_loaded_assets.put(it->second.asset->GetSessionId(), it->second.asset); + + const auto shader_resource = it->second.shader_resource; + m_loaded_assets.put(shader_resource->asset->GetSessionId(), shader_resource->asset); + for (const auto& [index, internal_texture_like_resource] : it->second.texture_like_resources) + { + std::visit(overloaded{[&](InternalTextureResource* texture_resource) { + m_loaded_assets.put(texture_resource->asset->GetSessionId(), + texture_resource->asset); + }, + [&](InternalInputRenderTargetResource* render_target_resource) { + m_loaded_assets.put(render_target_resource->asset->GetSessionId(), + render_target_resource->asset); + }}, + internal_texture_like_resource); + } + return &material_for_uid_iter->second; + } + } + + LoadMaterialAsset(asset_id, std::move(library), draw_data, &it->second); + + return nullptr; +} + +void CustomResourceManager::LoadMaterialAsset( + const CustomAssetLibrary::AssetID& asset_id, + std::shared_ptr library, + GraphicsModSystem::DrawDataView draw_data, InternalMaterialResource* internal_material) +{ + if (!internal_material->asset) + { + internal_material->asset = + CreateAsset(asset_id, AssetData::AssetType::Material, library); + internal_material->asset_data = + &m_session_id_to_asset_data[internal_material->asset->GetSessionId()]; + } + + const auto material_data = internal_material->asset->GetData(); + if (!material_data || + internal_material->asset_data->load_type == AssetData::LoadType::PendingReload) + { + // Tell the system we are still interested in loading this asset + const auto session_id = internal_material->asset->GetSessionId(); + m_pending_assets.put(session_id, m_session_id_to_asset_data[session_id].asset.get()); + return; + } + + const auto [it, inserted] = + m_shader_asset_cache.try_emplace(material_data->shader_asset, InternalShaderResource{}); + AssetData* shader_asset_data = it->second.asset_data; + if (!shader_asset_data || shader_asset_data->load_type == AssetData::LoadType::PendingReload) + { + LoadShaderAsset(material_data->shader_asset, library, draw_data.uid, &it->second); + return; + } + + if (shader_asset_data->has_errors) + { + internal_material->asset_data->has_errors = true; + return; + } + else + { + shader_asset_data->asset_owners.insert(internal_material->asset->GetSessionId()); + } + m_loaded_assets.put(it->second.asset->GetSessionId(), it->second.asset); + internal_material->shader_resource = &it->second; + + if (!LoadTextureAssetsFromMaterial(internal_material, library)) + return; + + if (!LoadOutputRenderTargetAssetsFromMaterial(internal_material, library)) + return; + + WriteMaterialUniforms(internal_material); + + const auto shader_data = it->second.asset->GetData(); + + // The shader system will take the custom shader and append on details of textures used during + // compilation. This means neither the material asset id, nor the shader asset id are a good fit. + // Instead, combine the shader asset id with a texture usage value, allowing the shader system to + // lookup shaders already generated with the same usage parameters + if (internal_material->pixel_shader_id.empty()) + { + internal_material->pixel_shader_id = fmt::format( + "{}-{}", shader_asset_data->asset->GetSessionId(), + GetTextureUsageHash(material_data->pixel_textures, shader_data->m_pixel_samplers)); + } + + if (internal_material->vertex_shader_id.empty()) + { + internal_material->vertex_shader_id = fmt::to_string(shader_asset_data->asset->GetSessionId()); + } + + if (material_data->next_material_asset != "") + { + if (GetMaterialFromAsset(material_data->next_material_asset, library, draw_data) == nullptr) + return; + const auto next_mat_it = m_material_asset_cache.find(material_data->next_material_asset); + if (next_mat_it == m_material_asset_cache.end()) + return; + internal_material->next = &next_mat_it->second; + } + internal_material->asset_data->load_type = AssetData::LoadType::LoadFinalyzed; +} + +void CustomResourceManager::LoadShaderAsset( + const CustomAssetLibrary::AssetID& asset_id, + std::shared_ptr library, const GXPipelineUid* uid, + InternalShaderResource* internal_shader) +{ + if (!internal_shader->asset) + { + internal_shader->asset = + CreateAsset(asset_id, AssetData::AssetType::Shader, library); + internal_shader->asset_data = + &m_session_id_to_asset_data[internal_shader->asset->GetSessionId()]; + } + + const auto shader_data = internal_shader->asset->GetData(); + if (!shader_data || internal_shader->asset_data->load_type == AssetData::LoadType::PendingReload) + { + // Tell the system we are still interested in loading this asset + const auto session_id = internal_shader->asset->GetSessionId(); + m_pending_assets.put(session_id, m_session_id_to_asset_data[session_id].asset.get()); + } +} + +bool CustomResourceManager::LoadTextureAssetsFromMaterial( + InternalMaterialResource* internal_material, + std::shared_ptr library) +{ + auto material_data = internal_material->asset->GetData(); + + auto internal_shader = internal_material->shader_resource; + auto shader_data = internal_shader->asset->GetData(); + + internal_material->texture_like_resources.clear(); + + for (std::size_t i = 0; i < material_data->pixel_textures.size(); i++) + { + const auto& texture_and_sampler = material_data->pixel_textures[i]; + if (texture_and_sampler.asset == "") + { + continue; + } + + if (texture_and_sampler.is_render_target) + { + const auto [it, inserted] = m_input_render_target_asset_cache.try_emplace( + texture_and_sampler.asset, InternalInputRenderTargetResource{}); + if (!LoadInputRenderTargetAsset(texture_and_sampler, library, &it->second)) + { + return false; + } + if (it->second.asset_data->has_errors) + { + internal_material->asset_data->has_errors = true; + } + else + { + it->second.asset_data->asset_owners.insert(internal_material->asset->GetSessionId()); + } + internal_material->texture_like_resources.emplace_back(i, &it->second); + } + else + { + const auto [it, inserted] = + m_texture_asset_cache.try_emplace(texture_and_sampler.asset, InternalTextureResource{}); + if (!LoadTextureAsset(texture_and_sampler, library, &it->second, + shader_data->m_pixel_samplers[i].type)) + { + return false; + } + if (it->second.asset_data->has_errors) + { + internal_material->asset_data->has_errors = true; + } + else + { + it->second.asset_data->asset_owners.insert(internal_material->asset->GetSessionId()); + } + internal_material->texture_like_resources.emplace_back(i, &it->second); + } + } + + return true; +} + +bool CustomResourceManager::LoadInputRenderTargetAsset( + const TextureSamplerValue& sampler_value, + std::shared_ptr library, + InternalInputRenderTargetResource* internal_render_target) +{ + if (!internal_render_target->asset) + { + internal_render_target->asset = CreateAsset( + sampler_value.asset, AssetData::AssetType::RenderTarget, library); + internal_render_target->asset_data = + &m_session_id_to_asset_data[internal_render_target->asset->GetSessionId()]; + } + + const auto render_target_data = internal_render_target->asset->GetData(); + if (!render_target_data || + internal_render_target->asset_data->load_type == AssetData::LoadType::PendingReload) + { + // Tell the system we are still interested in loading this asset + const auto session_id = internal_render_target->asset->GetSessionId(); + m_pending_assets.put(session_id, m_session_id_to_asset_data[session_id].asset.get()); + } + else if (internal_render_target->asset_data->load_type == AssetData::LoadType::LoadFinished) + { + if (!internal_render_target->asset_data->has_errors) + { + internal_render_target->sampler = CalculateSamplerAnsiotropy(render_target_data->m_sampler); + if (sampler_value.sampler_origin == VideoCommon::TextureSamplerValue::SamplerOrigin::Asset) + { + internal_render_target->texture_hash = ""; + } + else + { + internal_render_target->texture_hash = sampler_value.texture_hash; + } + internal_render_target->render_target_name = sampler_value.asset; + internal_render_target->camera_type = sampler_value.camera_type; + internal_render_target->camera_originating_draw_call = + sampler_value.camera_originating_draw_call; + } + + internal_render_target->asset_data->load_type = AssetData::LoadType::LoadFinalyzed; + return true; + } + + return false; +} + +bool CustomResourceManager::LoadTextureAsset( + const TextureSamplerValue& sampler_value, + std::shared_ptr library, + InternalTextureResource* internal_texture, AbstractTextureType texture_type) +{ + if (!internal_texture->asset) + { + internal_texture->asset = + CreateAsset(sampler_value.asset, AssetData::AssetType::Texture, library); + internal_texture->asset_data = + &m_session_id_to_asset_data[internal_texture->asset->GetSessionId()]; + m_custom_texture_cache.ReleaseToPool(sampler_value.asset); + } + + const auto texture_data = internal_texture->asset->GetData(); + if (!texture_data || + internal_texture->asset_data->load_type == AssetData::LoadType::PendingReload) + { + // Tell the system we are still interested in loading this asset + const auto session_id = internal_texture->asset->GetSessionId(); + m_pending_assets.put(session_id, m_session_id_to_asset_data[session_id].asset.get()); + } + else if (internal_texture->asset_data->load_type == AssetData::LoadType::LoadFinished) + { + if (!internal_texture->asset_data->has_errors) + { + const auto texture_result = m_custom_texture_cache.GetTextureFromData( + sampler_value.asset, texture_data->m_texture, texture_type); + + if (!texture_result) + return false; + + internal_texture->texture = *texture_result; + + internal_texture->sampler = CalculateSamplerAnsiotropy(texture_data->m_sampler); + if (!texture_data->m_texture.m_slices.empty() && + !texture_data->m_texture.m_slices[0].m_levels.empty()) + { + internal_texture->sampler.tm0.mipmap_filter = FilterMode::Linear; + } + if (sampler_value.sampler_origin == VideoCommon::TextureSamplerValue::SamplerOrigin::Asset) + { + internal_texture->texture_hash = ""; + } + else + { + internal_texture->texture_hash = sampler_value.texture_hash; + } + } + + internal_texture->asset_data->load_type = AssetData::LoadType::LoadFinalyzed; + return true; + } + else if (internal_texture->asset_data->load_type == AssetData::LoadType::LoadFinalyzed) + { + return true; + } + + return false; +} + +bool CustomResourceManager::LoadOutputRenderTargetAssetsFromMaterial( + InternalMaterialResource* internal_material, + std::shared_ptr library) +{ + auto material_data = internal_material->asset->GetData(); + + auto internal_shader = internal_material->shader_resource; + auto shader_data = internal_shader->asset->GetData(); + + for (std::size_t i = 0; i < material_data->render_targets.size(); i++) + { + const auto& render_target = material_data->render_targets[i]; + if (render_target == "") + { + continue; + } + + const auto [it, inserted] = m_output_render_target_asset_cache.try_emplace( + render_target, InternalOutputRenderTargetResource{}); + AssetData* render_target_asset_data = it->second.asset_data; + if (!render_target_asset_data || + render_target_asset_data->load_type == AssetData::LoadType::PendingReload) + { + LoadOutputRenderTargetAsset(render_target, library, &it->second); + return false; + } + else if (render_target_asset_data->load_type == AssetData::LoadType::LoadFinished) + { + render_target_asset_data->load_type = AssetData::LoadType::LoadFinalyzed; + + if (render_target_asset_data->has_errors) + { + internal_material->asset_data->has_errors = true; + return true; + } + else + { + render_target_asset_data->asset_owners.insert(internal_material->asset->GetSessionId()); + } + internal_material->output_render_target_resources.emplace_back(i, &it->second); + } + } + + return true; +} + +void CustomResourceManager::LoadOutputRenderTargetAsset( + const CustomAssetLibrary::AssetID& asset_id, + std::shared_ptr library, + InternalOutputRenderTargetResource* internal_render_target) +{ + if (!internal_render_target->asset) + { + internal_render_target->asset = + CreateAsset(asset_id, AssetData::AssetType::RenderTarget, library); + internal_render_target->asset_data = + &m_session_id_to_asset_data[internal_render_target->asset->GetSessionId()]; + } + + const auto render_target_data = internal_render_target->asset->GetData(); + if (!render_target_data || + internal_render_target->asset_data->load_type == AssetData::LoadType::PendingReload) + { + // Tell the system we are still interested in loading this asset + const auto session_id = internal_render_target->asset->GetSessionId(); + m_pending_assets.put(session_id, m_session_id_to_asset_data[session_id].asset.get()); + } +} + +GraphicsModSystem::MeshResource* +CustomResourceManager::GetMeshFromAsset(const CustomAssetLibrary::AssetID& asset_id, + std::shared_ptr library, + GraphicsModSystem::DrawDataView draw_data) +{ + const auto [it, inserted] = m_mesh_asset_cache.try_emplace(asset_id, InternalMeshResource{}); + if (it->second.asset_data && + it->second.asset_data->load_type == AssetData::LoadType::LoadFinalyzed) + { + return &it->second.mesh; + } + + LoadMeshAsset(asset_id, std::move(library), draw_data, &it->second); + return nullptr; +} + +void CustomResourceManager::LoadMeshAsset(const CustomAssetLibrary::AssetID& asset_id, + std::shared_ptr library, + GraphicsModSystem::DrawDataView draw_data, + InternalMeshResource* internal_mesh) +{ + if (!internal_mesh->asset) + { + internal_mesh->asset = CreateAsset(asset_id, AssetData::AssetType::Mesh, library); + internal_mesh->asset_data = &m_session_id_to_asset_data[internal_mesh->asset->GetSessionId()]; + } + + const auto mesh_data = internal_mesh->asset->GetData(); + if (!mesh_data || internal_mesh->asset_data->load_type == AssetData::LoadType::PendingReload) + { + // Tell the system we are still interested in loading this asset + const auto session_id = internal_mesh->asset->GetSessionId(); + m_pending_assets.put(session_id, m_session_id_to_asset_data[session_id].asset.get()); + + // Reset our mesh chunks... + internal_mesh->mesh_chunk_resources.clear(); + return; + } + + internal_mesh->mesh_chunk_resources.resize(mesh_data->m_mesh_chunks.size()); + for (std::size_t i = 0; i < mesh_data->m_mesh_chunks.size(); i++) + { + const auto& chunk = mesh_data->m_mesh_chunks[i]; + const auto material_asest_id_iter = + mesh_data->m_mesh_material_to_material_asset_id.find(chunk.material_name); + if (material_asest_id_iter == mesh_data->m_mesh_material_to_material_asset_id.end() || + material_asest_id_iter->second.empty()) + { + internal_mesh->mesh_chunk_resources[i] = InternalMeshChunkResource{}; + continue; + } + + if (internal_mesh->mesh_chunk_resources[i].native_vertex_format == nullptr) + { + auto vertex_declaration = chunk.vertex_declaration; + vertex_declaration.posmtx = draw_data.vertex_format->GetVertexDeclaration().posmtx; + internal_mesh->mesh_chunk_resources[i].native_vertex_format = + g_gfx->CreateNativeVertexFormat(vertex_declaration); + CalculateUidForCustomMesh(*draw_data.uid, chunk, &internal_mesh->mesh_chunk_resources[i]); + } + GraphicsModSystem::DrawDataView draw_data_custom_mesh; + draw_data_custom_mesh.gpu_skinning_normal_transform = {}; + draw_data_custom_mesh.gpu_skinning_position_transform = {}; + draw_data_custom_mesh.index_data = std::span(chunk.indices.get(), chunk.num_indices); + draw_data_custom_mesh.projection_type = draw_data.projection_type; + draw_data_custom_mesh.samplers = {}; + draw_data_custom_mesh.textures = {}; + draw_data_custom_mesh.uid = &internal_mesh->mesh_chunk_resources[i].uid; + draw_data_custom_mesh.vertex_data = + std::span(chunk.vertex_data.get(), chunk.num_vertices); + draw_data_custom_mesh.vertex_format = + internal_mesh->mesh_chunk_resources[i].native_vertex_format.get(); + internal_mesh->mesh_chunk_resources[i].material = + GetMaterialFromAsset(material_asest_id_iter->second, library, draw_data_custom_mesh); + if (!internal_mesh->mesh_chunk_resources[i].material) + return; + } + + internal_mesh->mesh.mesh_chunks.clear(); + for (std::size_t i = 0; i < mesh_data->m_mesh_chunks.size(); i++) + { + const auto& chunk = mesh_data->m_mesh_chunks[i]; + auto& internal_chunk_resource = internal_mesh->mesh_chunk_resources[i]; + if (!internal_chunk_resource.material) + continue; + + GraphicsModSystem::MeshChunkResource chunk_resource; + chunk_resource.components_available = chunk.components_available; + chunk_resource.index_data = std::span(chunk.indices.get(), chunk.num_indices); + chunk_resource.primitive_type = chunk.primitive_type; + chunk_resource.transform = chunk.transform; + chunk_resource.vertex_data = std::span(chunk.vertex_data.get(), chunk.num_vertices); + chunk_resource.vertex_format = internal_chunk_resource.native_vertex_format.get(); + chunk_resource.vertex_stride = internal_chunk_resource.native_vertex_format->GetVertexStride(); + chunk_resource.material = internal_chunk_resource.material; + internal_mesh->mesh.mesh_chunks.push_back(std::move(chunk_resource)); + } + + internal_mesh->asset_data->load_type = AssetData::LoadType::LoadFinalyzed; +} + +void CustomResourceManager::CalculateUidForCustomMesh( + const VideoCommon::GXPipelineUid& original, const VideoCommon::MeshDataChunk& mesh_chunk, + InternalMeshChunkResource* mesh_chunk_resource) +{ + mesh_chunk_resource->uid = original; + mesh_chunk_resource->uid.vertex_format = mesh_chunk_resource->native_vertex_format.get(); + vertex_shader_uid_data* const vs_uid_data = mesh_chunk_resource->uid.vs_uid.GetUidData(); + vs_uid_data->components = mesh_chunk.components_available; + + auto& tex_coords = mesh_chunk_resource->uid.vertex_format->GetVertexDeclaration().texcoords; + u32 texcoord_count = 0; + for (u32 i = 0; i < 8; ++i) + { + auto& texcoord = tex_coords[i]; + if (texcoord.enable) + { + if ((vs_uid_data->components & (VB_HAS_UV0 << i)) != 0) + { + auto& texinfo = vs_uid_data->texMtxInfo[texcoord_count]; + texinfo.texgentype = TexGenType::Passthrough; + texinfo.inputform = TexInputForm::ABC1; + texinfo.sourcerow = static_cast(static_cast(SourceRow::Tex0) + i); + } + texcoord_count++; + } + } + vs_uid_data->numTexGens = texcoord_count; + + auto& colors = mesh_chunk_resource->uid.vertex_format->GetVertexDeclaration().colors; + u32 color_count = 0; + for (u32 i = 0; i < 2; ++i) + { + auto& color = colors[i]; + if (color.enable) + { + color_count++; + } + } + vs_uid_data->numColorChans = color_count; + + vs_uid_data->dualTexTrans_enabled = false; + + pixel_shader_uid_data* const ps_uid_data = mesh_chunk_resource->uid.ps_uid.GetUidData(); + ps_uid_data->useDstAlpha = false; + + ps_uid_data->genMode_numindstages = 0; + ps_uid_data->genMode_numtevstages = 0; + ps_uid_data->genMode_numtexgens = vs_uid_data->numTexGens; + ps_uid_data->bounding_box = false; + ps_uid_data->rgba6_format = false; + ps_uid_data->dither = false; + ps_uid_data->uint_output = false; + + geometry_shader_uid_data* const gs_uid_data = mesh_chunk_resource->uid.gs_uid.GetUidData(); + gs_uid_data->primitive_type = static_cast(mesh_chunk.primitive_type); + gs_uid_data->numTexGens = vs_uid_data->numTexGens; + + mesh_chunk_resource->uid.rasterization_state.primitive = mesh_chunk.primitive_type; +} + +CustomTextureData* CustomResourceManager::GetTextureDataFromAsset( + const CustomAssetLibrary::AssetID& asset_id, + std::shared_ptr library) +{ + const auto [it, inserted] = + m_texture_data_asset_cache.try_emplace(asset_id, InternalTextureDataResource{}); + if (it->second.asset_data && + it->second.asset_data->load_type == AssetData::LoadType::LoadFinalyzed) + { + m_loaded_assets.put(it->second.asset->GetSessionId(), it->second.asset); + return &it->second.texture_data->m_texture; + } + + LoadTextureDataAsset(asset_id, std::move(library), &it->second); + + return nullptr; +} + +void CustomResourceManager::LoadTextureDataAsset( + const CustomAssetLibrary::AssetID& asset_id, + std::shared_ptr library, + InternalTextureDataResource* internal_texture_data) +{ + if (!internal_texture_data->asset) + { + internal_texture_data->asset = + CreateAsset(asset_id, AssetData::AssetType::TextureData, library); + internal_texture_data->asset_data = + &m_session_id_to_asset_data[internal_texture_data->asset->GetSessionId()]; + } + + auto texture_data = internal_texture_data->asset->GetData(); + if (!texture_data || + internal_texture_data->asset_data->load_type == AssetData::LoadType::PendingReload) + { + // Tell the system we are still interested in loading this asset + const auto session_id = internal_texture_data->asset->GetSessionId(); + m_pending_assets.put(session_id, m_session_id_to_asset_data[session_id].asset.get()); + } + else if (internal_texture_data->asset_data->load_type == AssetData::LoadType::LoadFinished) + { + internal_texture_data->texture_data = std::move(texture_data); + internal_texture_data->asset_data->load_type = AssetData::LoadType::LoadFinalyzed; + } +} + +void CustomResourceManager::CreateTextureResources( + GraphicsModSystem::DrawDataView draw_data, const InternalMaterialResource& internal_material, + GraphicsModSystem::MaterialResource* material) +{ + material->textures.clear(); + + for (const auto& [index, internal_texture_like_resource] : + internal_material.texture_like_resources) + { + std::visit(overloaded{[&](InternalTextureResource* internal_texture_resource) { + GraphicsModSystem::TextureResource texture_resource; + texture_resource.sampler_index = static_cast(index); + texture_resource.sampler = &internal_texture_resource->sampler; + texture_resource.texture = internal_texture_resource->texture; + texture_resource.texture_hash_for_sampler = + internal_texture_resource->texture_hash; + material->textures.push_back(std::move(texture_resource)); + }, + [&](InternalInputRenderTargetResource* internal_render_target_resource) { + GraphicsModSystem::InputRenderTargetResource render_target_resource; + render_target_resource.camera_originating_draw_call = + internal_render_target_resource->camera_originating_draw_call; + render_target_resource.camera_type = + internal_render_target_resource->camera_type; + render_target_resource.render_target_name = + internal_render_target_resource->render_target_name; + render_target_resource.sampler = + &internal_render_target_resource->sampler; + render_target_resource.sampler_index = static_cast(index); + render_target_resource.texture_hash_for_sampler = + internal_render_target_resource->texture_hash; + material->textures.push_back(std::move(render_target_resource)); + }}, + internal_texture_like_resource); + } +} + +void CustomResourceManager::CreateRenderTargetResources( + GraphicsModSystem::DrawDataView draw_data, const InternalMaterialResource& internal_material, + GraphicsModSystem::MaterialResource* material) +{ + material->render_targets.clear(); + + for (const auto& [index, internal_render_target_resource] : + internal_material.output_render_target_resources) + { + GraphicsModSystem::OutputRenderTargetResource render_target_resource; + render_target_resource.format = + internal_render_target_resource->asset->GetData()->m_texture_format; + render_target_resource.name = internal_render_target_resource->asset->GetAssetId(); + material->render_targets.push_back(std::move(render_target_resource)); + } +} + +bool CustomResourceManager::SetMaterialPipeline(GraphicsModSystem::DrawDataView draw_data, + InternalMaterialResource& internal_material, + GraphicsModSystem::MaterialResource* material) +{ + CustomPipelineMaterial pipeline_material_data; + + const auto material_data = internal_material.asset->GetData(); + auto shader_data = internal_material.shader_resource->asset->GetData(); + pipeline_material_data.shader = + CustomPipelineShader{.shader_data = std::move(shader_data), .material = material}; + + if (material_data->blending_state) + { + pipeline_material_data.blending_state = std::to_address(material_data->blending_state); + } + else + { + pipeline_material_data.blending_state = nullptr; + } + + if (material_data->depth_state) + { + pipeline_material_data.depth_state = std::to_address(material_data->depth_state); + } + else + { + pipeline_material_data.depth_state = nullptr; + } + + if (material_data->cull_mode) + { + pipeline_material_data.cull_mode = std::to_address(material_data->cull_mode); + } + else + { + pipeline_material_data.cull_mode = nullptr; + } + pipeline_material_data.id = internal_material.asset->GetAssetId(); + pipeline_material_data.pixel_shader_id = internal_material.pixel_shader_id; + pipeline_material_data.vertex_shader_id = internal_material.vertex_shader_id; + + const auto pipeline = + m_custom_shader_cache.GetPipelineAsync(*draw_data.uid, std::move(pipeline_material_data)); + if (!pipeline) + return false; + + material->pipeline = *pipeline; + + if (internal_material.next) + { + const auto [material_for_uid_iter, added] = + internal_material.next->material_per_uid.try_emplace(*draw_data.uid, + GraphicsModSystem::MaterialResource{}); + + if (!SetMaterialPipeline(draw_data, *internal_material.next, &material_for_uid_iter->second)) + return false; + material->next = &material_for_uid_iter->second; + } + + return true; +} + +void CustomResourceManager::WriteMaterialUniforms(InternalMaterialResource* internal_material) +{ + const auto material_data = internal_material->asset->GetData(); + + // Calculate the size in memory of the buffer + std::size_t max_pixeldata_size = 0; + for (const auto& property : material_data->pixel_properties) + { + max_pixeldata_size += VideoCommon::MaterialProperty2::GetMemorySize(property); + } + internal_material->pixel_data.resize(max_pixeldata_size); + + // Now write the memory + u8* pixel_data = internal_material->pixel_data.data(); + for (const auto& property : material_data->pixel_properties) + { + VideoCommon::MaterialProperty2::WriteToMemory(pixel_data, property); + } + + // Calculate the size in memory of the buffer + std::size_t max_vertexdata_size = 0; + for (const auto& property : material_data->vertex_properties) + { + max_vertexdata_size += VideoCommon::MaterialProperty2::GetMemorySize(property); + } + internal_material->vertex_data.resize(max_vertexdata_size); + + // Now write the memory + u8* vertex_data = internal_material->vertex_data.data(); + for (const auto& property : material_data->vertex_properties) + { + VideoCommon::MaterialProperty2::WriteToMemory(vertex_data, property); + } +} + +void CustomResourceManager::XFBTriggered(std::string_view texture_hash) +{ + std::set session_ids_reloaded_this_frame; + + // Look for any assets requested to be reloaded + { + decltype(m_assets_to_reload) assets_to_reload; + + if (m_reload_mutex.try_lock()) + { + std::swap(assets_to_reload, m_assets_to_reload); + m_reload_mutex.unlock(); + } + + for (const auto& asset_id : assets_to_reload) + { + const auto reload_dependency_from_type = [this, texture_hash](AssetData* asset_data) { + if (asset_data->type == AssetData::AssetType::Material) + { + std::list& resources = + m_pending_removals[std::string{texture_hash}]; + + m_custom_shader_cache.TakePipelineResource(asset_data->asset->GetAssetId(), &resources); + + auto& internal_material = m_material_asset_cache[asset_data->asset->GetAssetId()]; + m_custom_shader_cache.TakePixelShaderResource(internal_material.pixel_shader_id, + &resources); + m_custom_shader_cache.TakeVertexShaderResource(internal_material.vertex_shader_id, + &resources); + + internal_material.pixel_shader_id = ""; + internal_material.vertex_shader_id = ""; + } + }; + if (const auto it = m_asset_id_to_session_id.find(asset_id); + it != m_asset_id_to_session_id.end()) + { + const auto session_id = it->second; + session_ids_reloaded_this_frame.insert(session_id); + AssetData& asset_data = m_session_id_to_asset_data[session_id]; + asset_data.load_type = AssetData::LoadType::PendingReload; + asset_data.has_errors = false; + for (const auto owner_session_id : asset_data.asset_owners) + { + AssetData& owner_asset_data = m_session_id_to_asset_data[owner_session_id]; + if (owner_asset_data.load_type == AssetData::LoadType::LoadFinalyzed) + { + owner_asset_data.load_type = AssetData::LoadType::DependenciesChanged; + } + reload_dependency_from_type(&owner_asset_data); + } + m_pending_assets.put(it->second, asset_data.asset.get()); + } + } + } + + if (m_ram_used > m_max_ram_available) + { + const u64 threshold_ram = 0.8f * m_max_ram_available; + u64 ram_used = m_ram_used; + + // Clear out least recently used resources until + // we get safely in our threshold + while (ram_used > threshold_ram && m_loaded_assets.size() > 0) + { + const auto asset = m_loaded_assets.pop(); + ram_used -= asset->GetByteSizeInMemory(); + + AssetData& asset_data = m_session_id_to_asset_data[asset->GetSessionId()]; + + if (asset_data.type == AssetData::AssetType::Material) + { + m_material_asset_cache.erase(asset->GetAssetId()); + } + else if (asset_data.type == AssetData::AssetType::Mesh) + { + m_mesh_asset_cache.erase(asset->GetAssetId()); + } + else if (asset_data.type == AssetData::AssetType::RenderTarget) + { + m_output_render_target_asset_cache.erase(asset->GetAssetId()); + m_input_render_target_asset_cache.erase(asset->GetAssetId()); + } + else if (asset_data.type == AssetData::AssetType::Shader) + { + m_shader_asset_cache.erase(asset->GetAssetId()); + } + else if (asset_data.type == AssetData::AssetType::Texture) + { + m_texture_asset_cache.erase(asset->GetAssetId()); + } + else if (asset_data.type == AssetData::AssetType::TextureData) + { + m_texture_data_asset_cache.erase(asset->GetAssetId()); + } + asset_data.asset.reset(); + asset_data.load_type = AssetData::LoadType::Unloaded; + } + + // Recalculate to ensure accuracy + m_ram_used = 0; + for (const auto asset : m_loaded_assets.elements()) + { + m_ram_used += asset->GetByteSizeInMemory(); + } + } + + if (m_pending_assets.empty()) + return; + + const auto asset_session_ids_loaded = + m_asset_loader.LoadAssets(m_pending_assets.elements(), m_ram_used, m_max_ram_available); + for (const std::size_t session_id : asset_session_ids_loaded) + { + // While unlikely, if we loaded an asset in the previous frame but it was reloaded + // this frame, we should ignore this load and wait on the reload + if (session_ids_reloaded_this_frame.count(session_id) > 0) [[unlikely]] + continue; + + m_pending_assets.erase(session_id); + + AssetData& asset_data = m_session_id_to_asset_data[session_id]; + m_loaded_assets.put(session_id, asset_data.asset.get()); + asset_data.load_type = AssetData::LoadType::LoadFinished; + m_ram_used += asset_data.asset->GetByteSizeInMemory(); + + for (const auto owner_session_id : asset_data.asset_owners) + { + AssetData& owner_asset_data = m_session_id_to_asset_data[owner_session_id]; + if (owner_asset_data.load_type == AssetData::LoadType::LoadFinalyzed) + { + owner_asset_data.load_type = AssetData::LoadType::DependenciesChanged; + } + } + } +} + +void CustomResourceManager::FramePresented(const PresentInfo& present_info) +{ + for (const auto& xfb : present_info.xfb_copy_hashes) + { + // TODO: C++23 + m_pending_removals.erase(std::string{xfb}); + } +} + +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomResourceManager.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomResourceManager.h new file mode 100644 index 0000000000..fb78573504 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomResourceManager.h @@ -0,0 +1,354 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Common/CommonTypes.h" +#include "Common/SmallVector.h" + +#include "VideoCommon/AbstractPipeline.h" +#include "VideoCommon/AbstractTexture.h" +#include "VideoCommon/Assets/CustomAsset.h" +#include "VideoCommon/Assets/CustomAssetLibrary.h" +#include "VideoCommon/Assets/CustomAssetLoader2.h" +#include "VideoCommon/Constants.h" +#include "VideoCommon/GXPipelineTypes.h" +#include "VideoCommon/GraphicsModSystem/Runtime/CustomShaderCache2.h" +#include "VideoCommon/GraphicsModSystem/Runtime/CustomTextureCache2.h" +#include "VideoCommon/GraphicsModSystem/Types.h" +#include "VideoCommon/RenderState.h" + +struct PresentInfo; + +namespace VideoCommon +{ +class GameTextureAsset; +class MeshAsset; +class RasterMaterialAsset; +class RasterShaderAsset; +class RenderTargetAsset; +struct MeshDataChunk; + +class CustomResourceManager +{ +public: + void Initialize(); + void Shutdown(); + + void Reset(); + void SetHostConfig(const ShaderHostConfig& config); + + // Requests that an asset that exists be reloaded + void ReloadAsset(const CustomAssetLibrary::AssetID& asset_id); + + void XFBTriggered(std::string_view texture_hash); + void FramePresented(const PresentInfo& present_info); + + GraphicsModSystem::MeshResource* + GetMeshFromAsset(const CustomAssetLibrary::AssetID& asset_id, + std::shared_ptr library, + GraphicsModSystem::DrawDataView draw_data); + + GraphicsModSystem::MaterialResource* + GetMaterialFromAsset(const CustomAssetLibrary::AssetID& asset_id, + std::shared_ptr library, + GraphicsModSystem::DrawDataView draw_data); + + CustomTextureData* + GetTextureDataFromAsset(const CustomAssetLibrary::AssetID& asset_id, + std::shared_ptr library); + +private: + struct AssetData + { + std::unique_ptr asset; + CustomAssetLibrary::TimeType load_request_time = {}; + std::set asset_owners; + + enum class AssetType + { + Material, + Mesh, + RenderTarget, + Shader, + Texture, + TextureData + }; + AssetType type; + + enum class LoadType + { + PendingReload, + LoadFinished, + LoadFinalyzed, + DependenciesChanged, + Unloaded + }; + LoadType load_type = LoadType::PendingReload; + bool has_errors = false; + }; + + struct InternalTextureResource + { + AssetData* asset_data = nullptr; + VideoCommon::GameTextureAsset* asset = nullptr; + + AbstractTexture* texture = nullptr; + SamplerState sampler; + std::string_view texture_hash = ""; + }; + + struct InternalTextureDataResource + { + AssetData* asset_data = nullptr; + VideoCommon::GameTextureAsset* asset = nullptr; + std::shared_ptr texture_data; + }; + + struct InternalShaderResource + { + AssetData* asset_data = nullptr; + VideoCommon::RasterShaderAsset* asset = nullptr; + }; + + struct InternalInputRenderTargetResource + { + AssetData* asset_data = nullptr; + VideoCommon::RenderTargetAsset* asset = nullptr; + + std::string_view render_target_name; + GraphicsModSystem::CameraType camera_type = GraphicsModSystem::CameraType::None; + GraphicsModSystem::DrawCallID camera_originating_draw_call; + + SamplerState sampler; + std::string_view texture_hash = ""; + }; + + struct InternalOutputRenderTargetResource + { + AssetData* asset_data = nullptr; + VideoCommon::RenderTargetAsset* asset = nullptr; + }; + + struct InternalMaterialResource + { + AssetData* asset_data = nullptr; + VideoCommon::RasterMaterialAsset* asset = nullptr; + + InternalShaderResource* shader_resource = nullptr; + Common::SmallVector>, + VideoCommon::MAX_PIXEL_SHADER_SAMPLERS> + texture_like_resources; + Common::SmallVector, 8> + output_render_target_resources; + + std::string pixel_shader_id; + std::string vertex_shader_id; + + std::vector pixel_data; + std::vector vertex_data; + + std::map material_per_uid; + + InternalMaterialResource* next = nullptr; + }; + + struct InternalMeshChunkResource + { + std::unique_ptr native_vertex_format; + GraphicsModSystem::MaterialResource* material = nullptr; + VideoCommon::GXPipelineUid uid; + }; + + struct InternalMeshResource + { + AssetData* asset_data = nullptr; + VideoCommon::MeshAsset* asset = nullptr; + + std::vector mesh_chunk_resources; + + GraphicsModSystem::MeshResource mesh; + }; + + void LoadMeshAsset(const CustomAssetLibrary::AssetID& asset_id, + std::shared_ptr library, + GraphicsModSystem::DrawDataView, InternalMeshResource* internal_mesh); + + void LoadMaterialAsset(const CustomAssetLibrary::AssetID& asset_id, + std::shared_ptr library, + GraphicsModSystem::DrawDataView, + InternalMaterialResource* internal_material); + + void LoadShaderAsset(const CustomAssetLibrary::AssetID& asset_id, + std::shared_ptr library, + const GXPipelineUid* uid, InternalShaderResource* internal_shader); + + bool LoadTextureAssetsFromMaterial(InternalMaterialResource* internal_material, + std::shared_ptr library); + + bool LoadOutputRenderTargetAssetsFromMaterial( + InternalMaterialResource* internal_material, + std::shared_ptr library); + + void LoadOutputRenderTargetAsset(const CustomAssetLibrary::AssetID& asset_id, + std::shared_ptr library, + InternalOutputRenderTargetResource* internal_render_target); + + bool LoadInputRenderTargetAsset(const TextureSamplerValue& sampler_value, + std::shared_ptr library, + InternalInputRenderTargetResource* internal_render_target); + + bool LoadTextureAsset(const TextureSamplerValue& sampler_value, + std::shared_ptr library, + InternalTextureResource* internal_texture, + AbstractTextureType texture_type); + + void LoadTextureDataAsset(const CustomAssetLibrary::AssetID& asset_id, + std::shared_ptr library, + InternalTextureDataResource* internal_texture_data); + + void CreateTextureResources(GraphicsModSystem::DrawDataView draw_data, + const InternalMaterialResource& internal_material, + GraphicsModSystem::MaterialResource* material); + + void CreateRenderTargetResources(GraphicsModSystem::DrawDataView draw_data, + const InternalMaterialResource& internal_material, + GraphicsModSystem::MaterialResource* material); + + bool SetMaterialPipeline(GraphicsModSystem::DrawDataView draw_data, + InternalMaterialResource& internal_material, + GraphicsModSystem::MaterialResource* material); + + void WriteMaterialUniforms(InternalMaterialResource* internal_material); + + void CalculateUidForCustomMesh(const VideoCommon::GXPipelineUid& original, + const VideoCommon::MeshDataChunk& mesh_chunk, + InternalMeshChunkResource* mesh_chunk_resource); + + template + T* CreateAsset(const CustomAssetLibrary::AssetID& asset_id, AssetData::AssetType asset_type, + std::shared_ptr library) + { + const auto [it, added] = + m_asset_id_to_session_id.try_emplace(asset_id, m_session_id_to_asset_data.size()); + + if (added) + { + AssetData asset_data; + asset_data.asset = std::make_unique(library, asset_id, it->second); + asset_data.type = asset_type; + asset_data.has_errors = false; + asset_data.load_type = AssetData::LoadType::PendingReload; + asset_data.load_request_time = {}; + + m_session_id_to_asset_data.insert_or_assign(it->second, std::move(asset_data)); + + // Synchronize the priority cache session id + m_pending_assets.prepare(); + m_loaded_assets.prepare(); + } + auto& asset_data_from_session = m_session_id_to_asset_data[it->second]; + + // Asset got unloaded, rebuild it with the same metadata + if (!asset_data_from_session.asset) + { + asset_data_from_session.asset = std::make_unique(library, asset_id, it->second); + asset_data_from_session.has_errors = false; + asset_data_from_session.load_type = AssetData::LoadType::PendingReload; + } + + return static_cast(asset_data_from_session.asset.get()); + } + + class LeastRecentlyUsedCache + { + public: + const std::list& elements() const { return m_asset_cache; } + + void put(u64 asset_session_id, CustomAsset* asset) + { + erase(asset_session_id); + m_asset_cache.push_front(asset); + m_iterator_lookup[m_asset_cache.front()->GetSessionId()] = m_asset_cache.begin(); + } + + CustomAsset* pop() + { + if (m_asset_cache.empty()) [[unlikely]] + return nullptr; + const auto ret = m_asset_cache.back(); + if (ret != nullptr) + { + m_iterator_lookup[ret->GetSessionId()].reset(); + } + m_asset_cache.pop_back(); + return ret; + } + + void prepare() { m_iterator_lookup.push_back(std::nullopt); } + + void erase(u64 asset_session_id) + { + if (const auto iter = m_iterator_lookup[asset_session_id]) + { + m_asset_cache.erase(*iter); + m_iterator_lookup[asset_session_id].reset(); + } + } + + bool empty() const { return m_asset_cache.empty(); } + + std::size_t size() const { return m_asset_cache.size(); } + + private: + std::list m_asset_cache; + + // Note: this vector is expected to be kept in sync with + // the total amount of (unique) assets ever seen + std::vector> m_iterator_lookup; + }; + + LeastRecentlyUsedCache m_loaded_assets; + LeastRecentlyUsedCache m_pending_assets; + + std::map m_session_id_to_asset_data; + std::map m_asset_id_to_session_id; + + u64 m_ram_used = 0; + u64 m_max_ram_available = 0; + + std::map m_material_asset_cache; + std::map m_material_name_cache; + + std::map m_shader_asset_cache; + std::map m_texture_asset_cache; + std::map + m_output_render_target_asset_cache; + std::map + m_input_render_target_asset_cache; + std::map m_texture_data_asset_cache; + std::map m_mesh_asset_cache; + + std::map, std::less<>> m_pending_removals; + + std::mutex m_reload_mutex; + std::set m_assets_to_reload; + + CustomShaderCache2 m_custom_shader_cache; + CustomTextureCache2 m_custom_texture_cache; + CustomAssetLoader2 m_asset_loader; +}; + +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomShaderCache.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomShaderCache.cpp deleted file mode 100644 index cff6e7b6a6..0000000000 --- a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomShaderCache.cpp +++ /dev/null @@ -1,377 +0,0 @@ -// 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_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](Core::System&) { RetrieveAsyncShaders(); }, - "RetrieveAsyncShaders"); -} - -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_config(pipeline_config), - m_custom_shaders(custom_shaders) - { - 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_config(pipeline_config), - m_custom_shaders(custom_shaders) - { - 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 deleted file mode 100644 index ff2aba2823..0000000000 --- a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomShaderCache.h +++ /dev/null @@ -1,144 +0,0 @@ -// 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; -}; diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomShaderCache2.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomShaderCache2.cpp new file mode 100644 index 0000000000..567fc5c12b --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomShaderCache2.cpp @@ -0,0 +1,653 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModSystem/Runtime/CustomShaderCache2.h" + +#include "Common/Assert.h" +#include "Common/VariantUtil.h" + +#include "VideoCommon/AbstractGfx.h" +#include "VideoCommon/ConstantManager.h" +#include "VideoCommon/DriverDetails.h" +#include "VideoCommon/FramebufferManager.h" +#include "VideoCommon/VideoConfig.h" + +namespace +{ +/// Edits the UID based on driver bugs and other special configurations +static VideoCommon::GXPipelineUid ApplyDriverBugs(const VideoCommon::GXPipelineUid& in) +{ + VideoCommon::GXPipelineUid out; + // TODO: static_assert(std::is_trivially_copyable_v); + // GXPipelineUid is not trivially copyable because RasterizationState and BlendingState aren't + // either, but we can pretend it is for now. This will be improved after PR #10848 is finished. + memcpy(static_cast(&out), static_cast(&in), sizeof(out)); // copy padding + pixel_shader_uid_data* ps = out.ps_uid.GetUidData(); + BlendingState& blend = out.blending_state; + + if (ps->ztest == EmulatedZ::ForcedEarly && !out.depth_state.updateenable) + { + // No need to force early depth test if you're not writing z + ps->ztest = EmulatedZ::Early; + } + + // If framebuffer fetch is available, we can emulate logic ops in the fragment shader + // and don't need the below blend approximation + if (blend.logicopenable && !g_backend_info.bSupportsLogicOp && + !g_backend_info.bSupportsFramebufferFetch) + { + if (!blend.LogicOpApproximationIsExact()) + WARN_LOG_FMT(VIDEO, + "Approximating logic op with blending, this will produce incorrect rendering."); + if (blend.LogicOpApproximationWantsShaderHelp()) + { + ps->emulate_logic_op_with_blend = true; + ps->logic_op_mode = static_cast(blend.logicmode.Value()); + } + blend.ApproximateLogicOpWithBlending(); + } + + const bool benefits_from_ps_dual_source_off = + (!g_backend_info.bSupportsDualSourceBlend && g_backend_info.bSupportsFramebufferFetch) || + DriverDetails::HasBug(DriverDetails::BUG_BROKEN_DUAL_SOURCE_BLENDING); + if (benefits_from_ps_dual_source_off && !blend.RequiresDualSrc()) + { + // Only use dual-source blending when required on drivers that don't support it very well. + ps->no_dual_src = true; + blend.usedualsrc = false; + } + + if (g_backend_info.bSupportsFramebufferFetch) + { + bool fbfetch_blend = false; + if ((DriverDetails::HasBug(DriverDetails::BUG_BROKEN_DISCARD_WITH_EARLY_Z) || + !g_backend_info.bSupportsEarlyZ) && + ps->ztest == EmulatedZ::ForcedEarly) + { + ps->ztest = EmulatedZ::EarlyWithFBFetch; + fbfetch_blend |= static_cast(out.blending_state.blendenable); + ps->no_dual_src = true; + } + fbfetch_blend |= blend.logicopenable && !g_backend_info.bSupportsLogicOp; + fbfetch_blend |= blend.usedualsrc && !g_backend_info.bSupportsDualSourceBlend; + if (fbfetch_blend) + { + ps->no_dual_src = true; + if (blend.logicopenable) + { + ps->logic_op_enable = true; + ps->logic_op_mode = static_cast(blend.logicmode.Value()); + blend.logicopenable = false; + } + if (blend.blendenable) + { + ps->blend_enable = true; + ps->blend_src_factor = blend.srcfactor; + ps->blend_src_factor_alpha = blend.srcfactoralpha; + ps->blend_dst_factor = blend.dstfactor; + ps->blend_dst_factor_alpha = blend.dstfactoralpha; + ps->blend_subtract = blend.subtract; + ps->blend_subtract_alpha = blend.subtractAlpha; + blend.blendenable = false; + } + } + } + + // force dual src off if we can't support it + if (!g_backend_info.bSupportsDualSourceBlend) + { + ps->no_dual_src = true; + blend.usedualsrc = false; + } + + if (ps->ztest == EmulatedZ::ForcedEarly && !g_backend_info.bSupportsEarlyZ) + { + // These things should be false + ASSERT(!ps->zfreeze); + // ZCOMPLOC HACK: + // The only way to emulate alpha test + early-z is to force early-z in the shader. + // As this isn't available on all drivers and as we can't emulate this feature otherwise, + // we are only able to choose which one we want to respect more. + // Tests seem to have proven that writing depth even when the alpha test fails is more + // important that a reliable alpha test, so we just force the alpha test to always succeed. + // At least this seems to be less buggy. + ps->ztest = EmulatedZ::EarlyWithZComplocHack; + } + + if (g_ActiveConfig.UseVSForLinePointExpand() && + (out.rasterization_state.primitive == PrimitiveType::Points || + out.rasterization_state.primitive == PrimitiveType::Lines)) + { + // All primitives are expanded to triangles in the vertex shader + vertex_shader_uid_data* vs = out.vs_uid.GetUidData(); + const PortableVertexDeclaration& decl = out.vertex_format->GetVertexDeclaration(); + vs->position_has_3_elems = decl.position.components >= 3; + vs->texcoord_elem_count = 0; + for (int i = 0; i < 8; i++) + { + if (decl.texcoords[i].enable) + { + ASSERT(decl.texcoords[i].components <= 3); + vs->texcoord_elem_count |= decl.texcoords[i].components << (i * 2); + } + } + out.vertex_format = nullptr; + if (out.rasterization_state.primitive == PrimitiveType::Points) + vs->vs_expand = VSExpand::Point; + else + vs->vs_expand = VSExpand::Line; + PrimitiveType prim = g_backend_info.bSupportsPrimitiveRestart ? PrimitiveType::TriangleStrip : + PrimitiveType::Triangles; + out.rasterization_state.primitive = prim; + out.gs_uid.GetUidData()->primitive_type = static_cast(prim); + } + + return out; +} +} // namespace + +CustomShaderCache2::CustomShaderCache2() : m_api_type{APIType::Nothing} +{ +} + +CustomShaderCache2::~CustomShaderCache2() +{ + Shutdown(); +} + +void CustomShaderCache2::Initialize() +{ + m_api_type = g_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_frame_end_handler = AfterFrameEvent::Register([this](Core::System&) { RetrieveAsyncShaders(); }, + "CustomShaderCache2EndOfFrame"); +} + +void CustomShaderCache2::Shutdown() +{ + if (m_async_shader_compiler) + m_async_shader_compiler->StopWorkerThreads(); +} + +void CustomShaderCache2::RetrieveAsyncShaders() +{ + m_async_shader_compiler->RetrieveWorkItems(); +} + +void CustomShaderCache2::Reload() +{ + if (m_async_shader_compiler) + { + while (m_async_shader_compiler->HasPendingWork() || m_async_shader_compiler->HasCompletedWork()) + { + m_async_shader_compiler->RetrieveWorkItems(); + } + } + + m_ps_cache = {}; + m_vs_cache = {}; + m_pipeline_cache = {}; +} + +std::optional +CustomShaderCache2::GetPipelineAsync(const VideoCommon::GXPipelineUid& uid, + CustomPipelineMaterial custom_material) +{ + if (auto holder = m_pipeline_cache.GetHolder(uid, custom_material.id)) + { + if (holder->pending) + return std::nullopt; + return holder->value.get(); + } + AsyncCreatePipeline(uid, std::move(custom_material)); + return std::nullopt; +} + +void CustomShaderCache2::TakePipelineResource(const VideoCommon::CustomAssetLibrary::AssetID& id, + std::list* resources) +{ + std::list pipelines; + auto extracted = m_pipeline_cache.asset_id_to_cachelist.extract(id); + if (!extracted) + return; + for (auto& cache_element : extracted.mapped()) + { + resources->push_back(std::move(cache_element.second.value)); + } +} + +void CustomShaderCache2::TakeVertexShaderResource(const std::string& id, + std::list* resources) +{ + std::list shaders; + auto extracted = m_vs_cache.asset_id_to_cachelist.extract(id); + if (!extracted) + return; + for (auto& cache_element : extracted.mapped()) + { + resources->push_back(std::move(cache_element.second.value)); + } +} + +void CustomShaderCache2::TakePixelShaderResource(const std::string& id, + std::list* resources) +{ + std::list shaders; + auto extracted = m_ps_cache.asset_id_to_cachelist.extract(id); + if (!extracted) + return; + for (auto& cache_element : extracted.mapped()) + { + resources->push_back(std::move(cache_element.second.value)); + } +} + +void CustomShaderCache2::AsyncCreatePipeline(const VideoCommon::GXPipelineUid& uid, + CustomPipelineMaterial custom_material) +{ + class PipelineWorkItem final : public VideoCommon::AsyncShaderCompiler::WorkItem + { + public: + PipelineWorkItem(CustomShaderCache2* shader_cache, const VideoCommon::GXPipelineUid& uid, + CustomPipelineMaterial custom_material, PipelineIterator iterator) + : m_shader_cache(shader_cache), m_uid(uid), m_iterator(iterator), + m_custom_material(std::move(custom_material)) + { + SetStagesReady(); + } + + void SetStagesReady() + { + m_stages_ready = true; + + VideoCommon::GXPipelineUid new_uid = m_uid; + if (m_custom_material.blending_state) + new_uid.blending_state = *m_custom_material.blending_state; + + if (m_custom_material.depth_state) + new_uid.depth_state = *m_custom_material.depth_state; + + if (m_custom_material.cull_mode) + new_uid.rasterization_state.cullmode = *m_custom_material.cull_mode; + + const auto actual_uid = ApplyDriverBugs(new_uid); + + PixelShaderUid ps_uid = actual_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_material.pixel_shader_id)) + { + 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_material); + } + + VertexShaderUid vs_uid = actual_uid.vs_uid; + if (auto holder = + m_shader_cache->m_vs_cache.GetHolder(vs_uid, m_custom_material.vertex_shader_id)) + { + if (!holder->pending && holder->value.get()) + { + m_config.vertex_shader = holder->value.get(); + } + m_stages_ready &= !holder->pending; + } + else + { + m_stages_ready = false; + m_shader_cache->QueueVertexShaderCompile(vs_uid, m_custom_material); + } + + GeometryShaderUid gs_uid = actual_uid.gs_uid; + if (m_shader_cache->NeedsGeometryShader(gs_uid)) + { + if (auto holder = m_shader_cache->m_gs_cache.GetHolder(gs_uid, m_custom_material.id)) + { + if (!holder->pending && holder->value.get()) + { + m_config.geometry_shader = holder->value.get(); + } + m_stages_ready &= !holder->pending; + } + else + { + m_stages_ready = false; + m_shader_cache->QueueGeometryShaderCompile(gs_uid, m_custom_material); + } + } + + if (m_stages_ready) + { + if (m_custom_material.blending_state) + m_config.blending_state = *m_custom_material.blending_state; + else + m_config.blending_state = m_uid.blending_state; + + if (m_custom_material.depth_state) + m_config.depth_state = *m_custom_material.depth_state; + else + m_config.depth_state = m_uid.depth_state; + + m_config.framebuffer_state = g_framebuffer_manager->GetEFBFramebufferState(); + if (m_custom_material.additional_color_attachment_count > 0) + { + m_config.framebuffer_state.additional_color_attachment_count = + m_custom_material.additional_color_attachment_count; + } + + m_config.rasterization_state = m_uid.rasterization_state; + if (m_custom_material.cull_mode) + m_config.rasterization_state.cullmode = *m_custom_material.cull_mode; + + m_config.vertex_format = m_uid.vertex_format; + m_config.usage = AbstractPipelineUsage::GX; + } + } + + bool Compile() override + { + if (m_stages_ready) + { + // If either our vertex shader or pixel shader failed to compile + // don't even bother with a pipeline + if (!m_config.vertex_shader || !m_config.pixel_shader) + m_pipeline = nullptr; + else + 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, std::move(m_uid), std::move(m_custom_material), m_iterator); + m_shader_cache->m_async_shader_compiler->QueueWorkItem(std::move(wi), 0); + } + } + + private: + CustomShaderCache2* m_shader_cache; + std::unique_ptr m_pipeline; + VideoCommon::GXPipelineUid m_uid; + PipelineIterator m_iterator; + AbstractPipelineConfig m_config; + CustomPipelineMaterial m_custom_material; + bool m_stages_ready; + }; + + auto list_iter = m_pipeline_cache.InsertElement(uid, custom_material.id); + auto work_item = m_async_shader_compiler->CreateWorkItem( + this, uid, std::move(custom_material), list_iter); + m_async_shader_compiler->QueueWorkItem(std::move(work_item), 0); +} + +void CustomShaderCache2::NotifyPipelineFinished(PipelineIterator iterator, + std::unique_ptr pipeline) +{ + iterator->second.pending = false; + iterator->second.value = std::move(pipeline); +} + +bool CustomShaderCache2::NeedsGeometryShader(const GeometryShaderUid& uid) const +{ + return m_host_config.backend_geometry_shaders && !uid.GetUidData()->IsPassthrough(); +} + +void CustomShaderCache2::QueueGeometryShaderCompile(const GeometryShaderUid& uid, + const CustomPipelineMaterial& custom_material) +{ + class GeometryShaderWorkItem final : public VideoCommon::AsyncShaderCompiler::WorkItem + { + public: + GeometryShaderWorkItem(CustomShaderCache2* shader_cache, const GeometryShaderUid& uid, + const CustomPipelineMaterial& custom_material, + GeometryShaderIterator iter) + : m_shader_cache(shader_cache), m_uid(uid), m_custom_material(custom_material), m_iter(iter) + { + } + + bool Compile() override + { + m_shader = m_shader_cache->CompileGeometryShader(m_uid); + return true; + } + + void Retrieve() override + { + m_shader_cache->NotifyGeometryShaderFinished(m_iter, std::move(m_shader)); + } + + private: + CustomShaderCache2* m_shader_cache; + std::unique_ptr m_shader; + GeometryShaderUid m_uid; + CustomPipelineMaterial m_custom_material; + GeometryShaderIterator m_iter; + }; + + auto list_iter = m_gs_cache.InsertElement(uid, custom_material.id); + auto work_item = m_async_shader_compiler->CreateWorkItem( + this, uid, custom_material, list_iter); + m_async_shader_compiler->QueueWorkItem(std::move(work_item), 0); +} + +std::unique_ptr +CustomShaderCache2::CompileGeometryShader(const GeometryShaderUid& uid) const +{ + const ShaderCode source_code = + GenerateGeometryShaderCode(m_api_type, m_host_config, uid.GetUidData()); + return g_gfx->CreateShaderFromSource(ShaderStage::Geometry, source_code.GetBuffer(), + fmt::format("Geometry shader: {}", *uid.GetUidData())); +} + +void CustomShaderCache2::NotifyGeometryShaderFinished(GeometryShaderIterator iterator, + std::unique_ptr shader) +{ + iterator->second.pending = false; + iterator->second.value = std::move(shader); +} + +void CustomShaderCache2::QueuePixelShaderCompile(const PixelShaderUid& uid, + const CustomPipelineMaterial& custom_material) +{ + class PixelShaderWorkItem final : public VideoCommon::AsyncShaderCompiler::WorkItem + { + public: + PixelShaderWorkItem(CustomShaderCache2* shader_cache, const PixelShaderUid& uid, + const CustomPipelineMaterial& custom_material, PixelShaderIterator iter) + : m_shader_cache(shader_cache), m_uid(uid), m_custom_material(custom_material), m_iter(iter) + { + } + + bool Compile() override + { + ShaderCode shader_code; + + const std::size_t custom_sampler_index_offset = 8; + // Write out the samplers and defines for each texture + for (const auto& texture_like_resource : m_custom_material.shader.material->textures) + { + u32 sampler_index = 0; + std::visit( + overloaded{ + [&](const GraphicsModSystem::TextureResource& texture_resource) { + sampler_index = texture_resource.sampler_index; + }, + [&](const GraphicsModSystem::InputRenderTargetResource& render_target_resource) { + sampler_index = render_target_resource.sampler_index; + }}, + texture_like_resource); + const auto& sampler = m_custom_material.shader.shader_data->m_pixel_samplers[sampler_index]; + std::string_view sampler_type = ""; + switch (sampler.type) + { + case AbstractTextureType::Texture_2D: + sampler_type = "sampler2D"; + break; + case AbstractTextureType::Texture_2DArray: + sampler_type = "sampler2DArray"; + break; + case AbstractTextureType::Texture_CubeMap: + sampler_type = "samplerCube"; + break; + }; + shader_code.Write("SAMPLER_BINDING({}) uniform {} samp_{};\n", + custom_sampler_index_offset + sampler_index, sampler_type, sampler.name); + shader_code.Write("#define HAS_{} 1\n", sampler.name); + } + + const auto& output_targets = m_custom_material.shader.shader_data->m_output_targets; + if (!output_targets.empty()) + { + for (std::size_t i = 0; i < output_targets.size(); i++) + { + const auto& output_target = output_targets[i]; + const auto& render_target = m_custom_material.shader.material->render_targets[i]; + const u32 texel_size = AbstractTexture::GetTexelSizeForFormat(render_target.format); + + // TODO: vary type based on format + shader_code.Write("FRAGMENT_OUTPUT_LOCATION({}) out float{} render_target_{};\n", i + 1, + texel_size, output_target.name); + } + } + shader_code.Write("\n\n"); + + // Now write the custom shader + shader_code.Write( + "{}", ReplaceAll(m_custom_material.shader.shader_data->m_pixel_source, "\r\n", "\n")); + + // Write out the uniform data + ShaderCode uniform_code; + for (const auto& [name, property] : m_custom_material.shader.shader_data->m_pixel_properties) + { + VideoCommon::ShaderProperty2::WriteAsShaderCode(uniform_code, name, property); + } + if (!m_custom_material.shader.shader_data->m_pixel_properties.empty()) + uniform_code.Write("\n\n"); + + // Compile the shader + m_shader = m_shader_cache->CompilePixelShader(m_uid, shader_code.GetBuffer(), + uniform_code.GetBuffer()); + return true; + } + + void Retrieve() override + { + m_shader_cache->NotifyPixelShaderFinished(m_iter, std::move(m_shader)); + } + + private: + CustomShaderCache2* m_shader_cache; + std::unique_ptr m_shader; + PixelShaderUid m_uid; + CustomPipelineMaterial m_custom_material; + PixelShaderIterator m_iter; + }; + + auto list_iter = m_ps_cache.InsertElement(uid, custom_material.pixel_shader_id); + auto work_item = m_async_shader_compiler->CreateWorkItem( + this, uid, custom_material, list_iter); + m_async_shader_compiler->QueueWorkItem(std::move(work_item), 0); +} + +std::unique_ptr +CustomShaderCache2::CompilePixelShader(const PixelShaderUid& uid, std::string_view custom_fragment, + std::string_view custom_uniforms) const +{ + const ShaderCode source_code = PixelShader::WriteFullShader( + m_api_type, m_host_config, uid.GetUidData(), custom_fragment, custom_uniforms); + return g_gfx->CreateShaderFromSource(ShaderStage::Pixel, source_code.GetBuffer(), + "Custom Pixel Shader"); +} + +void CustomShaderCache2::NotifyPixelShaderFinished(PixelShaderIterator iterator, + std::unique_ptr shader) +{ + iterator->second.pending = false; + iterator->second.value = std::move(shader); +} + +void CustomShaderCache2::QueueVertexShaderCompile(const VertexShaderUid& uid, + const CustomPipelineMaterial& custom_material) +{ + class VertexShaderWorkItem final : public VideoCommon::AsyncShaderCompiler::WorkItem + { + public: + VertexShaderWorkItem(CustomShaderCache2* shader_cache, const VertexShaderUid& uid, + const CustomPipelineMaterial& custom_material, VertexShaderIterator iter) + : m_shader_cache(shader_cache), m_uid(uid), m_custom_material(custom_material), m_iter(iter) + { + } + + bool Compile() override + { + ShaderCode uniform_output; + + for (const auto& [name, property] : m_custom_material.shader.shader_data->m_vertex_properties) + { + VideoCommon::ShaderProperty2::WriteAsShaderCode(uniform_output, name, property); + } + + // Compile the shader + m_shader = m_shader_cache->CompileVertexShader( + m_uid, ReplaceAll(m_custom_material.shader.shader_data->m_vertex_source, "\r\n", "\n"), + uniform_output.GetBuffer()); + return true; + } + + void Retrieve() override + { + m_shader_cache->NotifyVertexShaderFinished(m_iter, std::move(m_shader)); + } + + private: + CustomShaderCache2* m_shader_cache; + std::unique_ptr m_shader; + VertexShaderUid m_uid; + CustomPipelineMaterial m_custom_material; + VertexShaderIterator m_iter; + }; + + auto list_iter = m_vs_cache.InsertElement(uid, custom_material.vertex_shader_id); + auto work_item = m_async_shader_compiler->CreateWorkItem( + this, uid, custom_material, list_iter); + m_async_shader_compiler->QueueWorkItem(std::move(work_item), 0); +} + +std::unique_ptr +CustomShaderCache2::CompileVertexShader(const VertexShaderUid& uid, std::string_view custom_vertex, + std::string_view custom_uniform) const +{ + const ShaderCode source_code = VertexShader::WriteFullShader( + m_api_type, m_host_config, uid.GetUidData(), custom_vertex, custom_uniform); + return g_gfx->CreateShaderFromSource(ShaderStage::Vertex, source_code.GetBuffer(), + "Custom Vertex Shader"); +} + +void CustomShaderCache2::NotifyVertexShaderFinished(VertexShaderIterator iterator, + std::unique_ptr shader) +{ + iterator->second.pending = false; + iterator->second.value = std::move(shader); +} diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomShaderCache2.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomShaderCache2.h new file mode 100644 index 0000000000..2861aa467e --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomShaderCache2.h @@ -0,0 +1,172 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "VideoCommon/AbstractPipeline.h" +#include "VideoCommon/AbstractShader.h" +#include "VideoCommon/Assets/CustomAssetLibrary.h" +#include "VideoCommon/Assets/MaterialAsset.h" +#include "VideoCommon/Assets/ShaderAsset.h" +#include "VideoCommon/AsyncShaderCompiler.h" +#include "VideoCommon/GXPipelineTypes.h" +#include "VideoCommon/GraphicsModSystem/Types.h" +#include "VideoCommon/PixelShaderGen.h" +#include "VideoCommon/RenderState.h" +#include "VideoCommon/ShaderCache.h" +#include "VideoCommon/ShaderGenCommon.h" +#include "VideoCommon/VertexShaderGen.h" +#include "VideoCommon/VideoEvents.h" + +struct CustomPipelineShader +{ + std::shared_ptr shader_data; + const GraphicsModSystem::MaterialResource* material; +}; + +struct CustomPipelineMaterial +{ + CustomPipelineShader shader; + + const BlendingState* blending_state = nullptr; + const DepthState* depth_state = nullptr; + const CullMode* cull_mode = nullptr; + + u32 additional_color_attachment_count = 0; + + VideoCommon::CustomAssetLibrary::AssetID id; + std::string pixel_shader_id; + std::string vertex_shader_id; +}; + +class CustomShaderCache2 +{ +public: + CustomShaderCache2(); + ~CustomShaderCache2(); + CustomShaderCache2(const CustomShaderCache2&) = delete; + CustomShaderCache2(CustomShaderCache2&&) = delete; + CustomShaderCache2& operator=(const CustomShaderCache2&) = delete; + CustomShaderCache2& operator=(CustomShaderCache2&&) = delete; + + void Initialize(); + void Shutdown(); + + // Changes the shader host config. Shaders should be reloaded afterwards. + void SetHostConfig(const ShaderHostConfig& host_config) { m_host_config.bits = host_config.bits; } + + // Whether geometry shaders are needed + bool NeedsGeometryShader(const GeometryShaderUid& uid) const; + + // 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, + CustomPipelineMaterial custom_material); + + using Resource = std::variant, std::unique_ptr>; + void TakePipelineResource(const VideoCommon::CustomAssetLibrary::AssetID& id, + std::list* resources); + void TakeVertexShaderResource(const std::string& id, std::list* resources); + void TakePixelShaderResource(const std::string& id, std::list* resources); + +private: + // Configuration bits. + APIType m_api_type = APIType::Nothing; + ShaderHostConfig m_host_config = {}; + std::unique_ptr m_async_shader_compiler; + + void AsyncCreatePipeline(const VideoCommon::GXPipelineUid& uid, + CustomPipelineMaterial custom_material); + + // 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 asset_id_to_cachelist; + + const CacheHolder* GetHolder(const Uid& uid, + const VideoCommon::CustomAssetLibrary::AssetID& asset_id) const + { + if (auto asset_it = asset_id_to_cachelist.find(asset_id); + asset_it != asset_id_to_cachelist.end()) + { + for (const auto& [stored_uid, holder] : asset_it->second) + { + if (uid == stored_uid) + { + return &holder; + } + } + } + + return nullptr; + } + + typename CacheList::iterator + InsertElement(const Uid& uid, const VideoCommon::CustomAssetLibrary::AssetID& asset_id) + { + CacheList& cachelist = asset_id_to_cachelist[asset_id]; + CacheElement e{uid, CacheHolder{}}; + return cachelist.emplace(cachelist.begin(), std::move(e)); + } + }; + + Cache m_gs_cache; + Cache m_ps_cache; + Cache m_vs_cache; + Cache m_pipeline_cache; + + using GeometryShaderIterator = Cache::CacheList::iterator; + using PipelineIterator = Cache::CacheList::iterator; + using PixelShaderIterator = Cache::CacheList::iterator; + using VertexShaderIterator = Cache::CacheList::iterator; + + void NotifyPipelineFinished(PipelineIterator iterator, + std::unique_ptr pipeline); + + std::unique_ptr CompileGeometryShader(const GeometryShaderUid& uid) const; + void NotifyGeometryShaderFinished(GeometryShaderIterator iterator, + std::unique_ptr shader); + void QueueGeometryShaderCompile(const GeometryShaderUid& uid, + const CustomPipelineMaterial& custom_material); + + std::unique_ptr CompilePixelShader(const PixelShaderUid& uid, + std::string_view custom_fragment, + std::string_view custom_uniforms) const; + void NotifyPixelShaderFinished(PixelShaderIterator iterator, + std::unique_ptr shader); + void QueuePixelShaderCompile(const PixelShaderUid& uid, + const CustomPipelineMaterial& custom_material); + + std::unique_ptr CompileVertexShader(const VertexShaderUid& uid, + std::string_view custom_vertex, + std::string_view custom_uniform) const; + void NotifyVertexShaderFinished(VertexShaderIterator iterator, + std::unique_ptr shader); + void QueueVertexShaderCompile(const VertexShaderUid& uid, + const CustomPipelineMaterial& custom_material); + + Common::EventHook m_frame_end_handler; +}; diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomTextureCache.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomTextureCache.cpp new file mode 100644 index 0000000000..db95526800 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomTextureCache.cpp @@ -0,0 +1,193 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModSystem/Runtime/CustomTextureCache.h" + +#include + +#include "Common/Logging/Log.h" + +#include "VideoCommon/AbstractFramebuffer.h" +#include "VideoCommon/AbstractGfx.h" +#include "VideoCommon/AbstractTexture.h" +#include "VideoCommon/Assets/CustomAssetLoader.h" +#include "VideoCommon/VideoEvents.h" + +namespace VideoCommon +{ +CustomTextureCache::CustomTextureCache() +{ + m_frame_event = + AfterFrameEvent::Register([this](Core::System&) { OnFrameEnd(); }, "CustomTextureCache"); +} + +CustomTextureCache::~CustomTextureCache() = default; + +std::optional CustomTextureCache::GetTextureAsset( + CustomAssetLoader& loader, std::shared_ptr library, + const CustomAssetLibrary::AssetID& asset_id, AbstractTextureType texture_type) +{ + const auto [iter, inserted] = m_cached_texture_assets.try_emplace(asset_id, CachedTextureAsset{}); + if (inserted || !iter->second.cached_asset.m_asset || + iter->second.cached_asset.m_asset->GetLastLoadedTime() > + iter->second.cached_asset.m_cached_write_time || + asset_id != iter->second.cached_asset.m_asset->GetAssetId() || !iter->second.texture || + iter->second.texture->GetConfig().type != texture_type) + { + auto asset = loader.LoadGameTexture(asset_id, library); + const auto loaded_time = asset->GetLastLoadedTime(); + iter->second.cached_asset = + VideoCommon::CachedAsset{std::move(asset), loaded_time}; + ReleaseToPool(&iter->second); + } + else + { + loader.AssetReferenced(iter->second.cached_asset.m_asset->GetSessionId()); + } + iter->second.time = std::chrono::steady_clock::now(); + + const auto texture_data = iter->second.cached_asset.m_asset->GetData(); + if (!texture_data) + { + return std::nullopt; + } + + if (iter->second.texture) + { + return TextureResult{iter->second.texture.get(), iter->second.framebuffer.get(), texture_data}; + } + + auto& first_slice = texture_data->m_texture.m_slices[0]; + const TextureConfig texture_config(first_slice.m_levels[0].width, first_slice.m_levels[0].height, + static_cast(first_slice.m_levels.size()), + static_cast(texture_data->m_texture.m_slices.size()), 1, + first_slice.m_levels[0].format, 0, texture_type); + + auto new_texture = AllocateTexture(texture_config); + if (!new_texture) + { + ERROR_LOG_FMT(VIDEO, "Custom texture creation failed due to texture allocation failure"); + return std::nullopt; + } + + iter->second.texture.swap(new_texture->texture); + iter->second.framebuffer.swap(new_texture->framebuffer); + for (std::size_t slice_index = 0; slice_index < texture_data->m_texture.m_slices.size(); + slice_index++) + { + auto& slice = texture_data->m_texture.m_slices[slice_index]; + for (u32 level_index = 0; level_index < static_cast(slice.m_levels.size()); ++level_index) + { + auto& level = slice.m_levels[level_index]; + iter->second.texture->Load(level_index, level.width, level.height, level.row_length, + level.data.data(), level.data.size(), + static_cast(slice_index)); + } + } + + return TextureResult{iter->second.texture.get(), iter->second.framebuffer.get(), texture_data}; +} + +CustomTextureCache::TexPoolEntry::TexPoolEntry(std::unique_ptr tex, + std::unique_ptr fb) + : texture(std::move(tex)), framebuffer(std::move(fb)), time(std::chrono::steady_clock::now()) +{ +} + +std::optional +CustomTextureCache::AllocateTexture(const TextureConfig& config) +{ + TexPool::iterator iter = FindMatchingTextureFromPool(config); + if (iter != m_texture_pool.end()) + { + auto entry = std::move(iter->second); + m_texture_pool.erase(iter); + return std::move(entry); + } + + std::unique_ptr texture = g_gfx->CreateTexture(config); + if (!texture) + { + WARN_LOG_FMT(VIDEO, "Failed to allocate a {}x{}x{} texture", config.width, config.height, + config.layers); + return {}; + } + + std::unique_ptr framebuffer; + if (config.IsRenderTarget()) + { + framebuffer = g_gfx->CreateFramebuffer(texture.get(), nullptr); + if (!framebuffer) + { + WARN_LOG_FMT(VIDEO, "Failed to allocate a {}x{}x{} framebuffer", config.width, config.height, + config.layers); + return {}; + } + } + + return TexPoolEntry(std::move(texture), std::move(framebuffer)); +} + +CustomTextureCache::TexPool::iterator +CustomTextureCache::FindMatchingTextureFromPool(const TextureConfig& config) +{ + // Find a texture from the pool that does not have a frameCount of FRAMECOUNT_INVALID. + // This prevents a texture from being used twice in a single frame with different data, + // which potentially means that a driver has to maintain two copies of the texture anyway. + // Render-target textures are fine through, as they have to be generated in a seperated pass. + // As non-render-target textures are usually static, this should not matter much. + auto range = m_texture_pool.equal_range(config); + auto matching_iter = std::find_if(range.first, range.second, + [](const auto& iter) { return iter.first.IsRenderTarget(); }); + return matching_iter != range.second ? matching_iter : m_texture_pool.end(); +} + +void CustomTextureCache::ReleaseToPool(CachedTextureAsset* entry) +{ + if (!entry->texture) + return; + auto config = entry->texture->GetConfig(); + m_texture_pool.emplace(config, + TexPoolEntry(std::move(entry->texture), std::move(entry->framebuffer))); + entry->texture = nullptr; + entry->framebuffer = nullptr; +} + +void CustomTextureCache::OnFrameEnd() +{ + // Iterate over outstanding textures + { + auto iter = m_cached_texture_assets.begin(); + const auto end = m_cached_texture_assets.end(); + while (iter != end) + { + if ((std::chrono::steady_clock::now() - iter->second.time) > std::chrono::milliseconds{500}) + { + ReleaseToPool(&iter->second); + iter = m_cached_texture_assets.erase(iter); + } + else + { + ++iter; + } + } + } + + // Iterate over pool + { + auto iter = m_texture_pool.begin(); + const auto end = m_texture_pool.end(); + while (iter != end) + { + if ((std::chrono::steady_clock::now() - iter->second.time) > std::chrono::milliseconds{250}) + { + iter = m_texture_pool.erase(iter); + } + else + { + ++iter; + } + } + } +} +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomTextureCache.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomTextureCache.h new file mode 100644 index 0000000000..38979a932b --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomTextureCache.h @@ -0,0 +1,72 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#include "Common/CommonTypes.h" +#include "Common/HookableEvent.h" +#include "VideoCommon/Assets/CustomAssetLibrary.h" +#include "VideoCommon/Assets/TextureAsset.h" +#include "VideoCommon/TextureConfig.h" + +class AbstractFramebuffer; +class AbstractTexture; + +namespace VideoCommon +{ +class CustomAssetLoader; +class CustomTextureCache +{ +public: + CustomTextureCache(); + ~CustomTextureCache(); + + struct TextureResult + { + AbstractTexture* texture; + AbstractFramebuffer* framebuffer; + std::shared_ptr data; + }; + std::optional GetTextureAsset(CustomAssetLoader& loader, + std::shared_ptr library, + const CustomAssetLibrary::AssetID& asset_id, + AbstractTextureType texture_type); + +private: + struct TexPoolEntry + { + std::unique_ptr texture; + std::unique_ptr framebuffer; + std::chrono::steady_clock::time_point time; + + TexPoolEntry(std::unique_ptr tex, std::unique_ptr fb); + }; + + using TexPool = std::unordered_multimap; + + std::optional AllocateTexture(const TextureConfig& config); + TexPool::iterator FindMatchingTextureFromPool(const TextureConfig& config); + + TexPool m_texture_pool; + + struct CachedTextureAsset + { + VideoCommon::CachedAsset cached_asset; + std::unique_ptr texture; + std::unique_ptr framebuffer; + std::chrono::steady_clock::time_point time; + }; + + void ReleaseToPool(CachedTextureAsset* entry); + + std::unordered_map + m_cached_texture_assets; + + void OnFrameEnd(); + Common::EventHook m_frame_event; +}; +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomTextureCache2.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomTextureCache2.cpp new file mode 100644 index 0000000000..8826359150 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomTextureCache2.cpp @@ -0,0 +1,128 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModSystem/Runtime/CustomTextureCache2.h" + +#include "Common/Logging/Log.h" + +#include "VideoCommon/AbstractGfx.h" +#include "VideoCommon/AbstractTexture.h" + +namespace VideoCommon +{ +CustomTextureCache2::CustomTextureCache2() = default; +CustomTextureCache2::~CustomTextureCache2() = default; + +void CustomTextureCache2::Reset() +{ + m_cached_textures.clear(); + m_texture_pool.clear(); +} + +std::optional +CustomTextureCache2::GetTextureFromData(const VideoCommon::CustomAssetLibrary::AssetID& asset_id, + const CustomTextureData& data, + AbstractTextureType texture_type) +{ + const auto [iter, inserted] = m_cached_textures.try_emplace(asset_id, nullptr); + + if (iter->second) + { + return iter->second.get(); + } + + auto& first_slice = data.m_slices[0]; + const TextureConfig texture_config(first_slice.m_levels[0].width, first_slice.m_levels[0].height, + static_cast(first_slice.m_levels.size()), + static_cast(data.m_slices.size()), 1, + first_slice.m_levels[0].format, 0, texture_type); + + auto new_texture = AllocateTexture(texture_config); + if (!new_texture) + { + ERROR_LOG_FMT(VIDEO, "Custom texture creation failed due to texture allocation failure"); + return std::nullopt; + } + + std::swap(iter->second, *new_texture); + for (std::size_t slice_index = 0; slice_index < data.m_slices.size(); slice_index++) + { + auto& slice = data.m_slices[slice_index]; + for (u32 level_index = 0; level_index < static_cast(slice.m_levels.size()); ++level_index) + { + auto& level = slice.m_levels[level_index]; + iter->second->Load(level_index, level.width, level.height, level.row_length, + level.data.data(), level.data.size(), static_cast(slice_index)); + } + } + + return iter->second.get(); +} + +std::optional +CustomTextureCache2::GetTextureFromConfig(const VideoCommon::CustomAssetLibrary::AssetID& asset_id, + const TextureConfig& config) +{ + const auto [iter, inserted] = m_cached_textures.try_emplace(asset_id, nullptr); + + if (iter->second) + { + return iter->second.get(); + } + + auto new_texture = AllocateTexture(config); + if (!new_texture) + { + ERROR_LOG_FMT(VIDEO, "Custom texture creation failed due to texture allocation failure"); + return std::nullopt; + } + std::swap(iter->second, *new_texture); + + return iter->second.get(); +} + +std::optional> +CustomTextureCache2::AllocateTexture(const TextureConfig& config) +{ + TexPool::iterator iter = FindMatchingTextureFromPool(config); + if (iter != m_texture_pool.end()) + { + auto entry = std::move(iter->second); + m_texture_pool.erase(iter); + return std::move(entry); + } + + std::unique_ptr texture = g_gfx->CreateTexture(config); + if (!texture) + { + WARN_LOG_FMT(VIDEO, "Failed to allocate a {}x{}x{} texture", config.width, config.height, + config.layers); + return {}; + } + return texture; +} + +CustomTextureCache2::TexPool::iterator +CustomTextureCache2::FindMatchingTextureFromPool(const TextureConfig& config) +{ + // Find a texture from the pool that does not have a frameCount of FRAMECOUNT_INVALID. + // This prevents a texture from being used twice in a single frame with different data, + // which potentially means that a driver has to maintain two copies of the texture anyway. + // Render-target textures are fine through, as they have to be generated in a seperated pass. + // As non-render-target textures are usually static, this should not matter much. + auto range = m_texture_pool.equal_range(config); + auto matching_iter = std::find_if(range.first, range.second, + [](const auto& iter) { return iter.first.IsRenderTarget(); }); + return matching_iter != range.second ? matching_iter : m_texture_pool.end(); +} + +void CustomTextureCache2::ReleaseToPool(const VideoCommon::CustomAssetLibrary::AssetID& asset_id) +{ + const auto it = m_cached_textures.find(asset_id); + if (it == m_cached_textures.end()) + return; + auto config = it->second->GetConfig(); + m_texture_pool.emplace(config, std::move(it->second)); + m_cached_textures.erase(it); +} +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomTextureCache2.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomTextureCache2.h new file mode 100644 index 0000000000..aed447112f --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomTextureCache2.h @@ -0,0 +1,45 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#include "Common/CommonTypes.h" +#include "VideoCommon/Assets/CustomAssetLibrary.h" +#include "VideoCommon/Assets/CustomTextureData.h" +#include "VideoCommon/TextureConfig.h" + +class AbstractTexture; + +namespace VideoCommon +{ +class CustomTextureCache2 +{ +public: + CustomTextureCache2(); + ~CustomTextureCache2(); + + void Reset(); + + std::optional + GetTextureFromData(const VideoCommon::CustomAssetLibrary::AssetID& asset_id, + const CustomTextureData& data, AbstractTextureType texture_type); + std::optional + GetTextureFromConfig(const VideoCommon::CustomAssetLibrary::AssetID& asset_id, + const TextureConfig& config); + void ReleaseToPool(const VideoCommon::CustomAssetLibrary::AssetID& asset_id); + +private: + using TexPool = std::unordered_multimap>; + + std::optional> AllocateTexture(const TextureConfig& config); + TexPool::iterator FindMatchingTextureFromPool(const TextureConfig& config); + + TexPool m_texture_pool; + std::unordered_map> + m_cached_textures; +}; +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h index 04a371a73e..b7d5c43978 100644 --- a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h @@ -3,7 +3,10 @@ #pragma once +#include + #include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionData.h" +#include "VideoCommon/GraphicsModSystem/Types.h" class GraphicsModAction { @@ -22,5 +25,22 @@ public: virtual void OnProjectionAndTexture(GraphicsModActionData::Projection*) {} virtual void OnTextureLoad(GraphicsModActionData::TextureLoad*) {} virtual void OnTextureCreate(GraphicsModActionData::TextureCreate*) {} + virtual void OnLight(GraphicsModActionData::Light*) {} virtual void OnFrameEnd() {} + + void SetID(u64 id) { m_id = id; } + u64 GetID() const { return m_id; } + + void SetDrawCall(GraphicsModSystem::DrawCallID draw_call) { m_draw_call = draw_call; } + GraphicsModSystem::DrawCallID GetDrawCall() const { return m_draw_call; } + + virtual void DrawImGui() {} + virtual void SerializeToConfig(picojson::object* obj) {} + virtual std::string GetFactoryName() const { return ""; } + +protected: + GraphicsModSystem::DrawCallID m_draw_call = GraphicsModSystem::DrawCallID::INVALID; + +private: + u64 m_id = 0; }; diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionData.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionData.h index cecd0ce94f..73d1f796cb 100644 --- a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionData.h +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionData.h @@ -12,17 +12,24 @@ #include "Common/CommonTypes.h" #include "Common/Matrix.h" #include "Common/SmallVector.h" +#include "VideoCommon/AbstractGfx.h" #include "VideoCommon/Assets/TextureAsset.h" +#include "VideoCommon/ConstantManager.h" +#include "VideoCommon/GraphicsModSystem/Types.h" #include "VideoCommon/PixelShaderGen.h" namespace GraphicsModActionData { struct DrawStarted { - const Common::SmallVector& texture_units; + const GraphicsModSystem::DrawDataView& draw_data_view; + u32 current_components_available; bool* skip; - std::optional* custom_pixel_shader; - std::span* material_uniform_buffer; + GraphicsModSystem::MaterialResource** material; + GraphicsModSystem::MeshResource** mesh; + bool* ignore_mesh_transform; + std::optional* transform; + std::optional* camera; }; struct EFB @@ -34,6 +41,16 @@ struct EFB u32* scaled_height; }; +struct Light +{ + int4* color; + float4* cosatt; + float4* distatt; + float4* pos; + float4* dir; + bool* skip; +}; + struct Projection { Common::Matrix44* matrix; diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.cpp index 1059595ef1..93873ea307 100644 --- a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.cpp +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.cpp @@ -3,11 +3,14 @@ #include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.h" +#include "VideoCommon/GraphicsModSystem/Runtime/Actions/CustomMeshAction.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/RelativeCameraAction.h" #include "VideoCommon/GraphicsModSystem/Runtime/Actions/ScaleAction.h" #include "VideoCommon/GraphicsModSystem/Runtime/Actions/SkipAction.h" +#include "VideoCommon/GraphicsModSystem/Runtime/Actions/TransformAction.h" namespace GraphicsModActionFactory { @@ -34,6 +37,18 @@ std::unique_ptr Create(std::string_view name, const picojson: { return CustomPipelineAction::Create(json_data, std::move(library)); } + else if (name == TransformAction::factory_name) + { + return TransformAction::Create(json_data); + } + else if (name == CustomMeshAction::factory_name) + { + return CustomMeshAction::Create(json_data, std::move(library)); + } + else if (name == RelativeCameraAction::factory_name) + { + return RelativeCameraAction::Create(json_data, std::move(library)); + } return nullptr; } diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModBackend.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModBackend.cpp new file mode 100644 index 0000000000..d935d6867e --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModBackend.cpp @@ -0,0 +1,126 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModBackend.h" + +#include "Common/SmallVector.h" +#include "Core/System.h" + +#include "VideoCommon/GXPipelineTypes.h" +#include "VideoCommon/GeometryShaderManager.h" +#include "VideoCommon/GraphicsModSystem/Runtime/CustomResourceManager.h" +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h" +#include "VideoCommon/PixelShaderManager.h" +#include "VideoCommon/VertexLoaderManager.h" +#include "VideoCommon/VertexShaderManager.h" +#include "VideoCommon/XFMemory.h" + +namespace GraphicsModSystem::Runtime +{ +namespace +{ +bool IsDrawGPUSkinned(NativeVertexFormat* format, PrimitiveType primitive_type) +{ + if (primitive_type != PrimitiveType::Triangles && primitive_type != PrimitiveType::TriangleStrip) + { + return false; + } + + const PortableVertexDeclaration vert_decl = format->GetVertexDeclaration(); + return vert_decl.posmtx.enable; +} +} // namespace + +void GraphicsModBackend::OnTextureCreate(const TextureView& texture) +{ + if (texture.texture_type == TextureType::XFB) + { + auto& system = Core::System::GetInstance(); + auto& custom_resource_manager = system.GetCustomResourceManager(); + custom_resource_manager.XFBTriggered(texture.hash_name); + } +} + +void GraphicsModBackend::OnFramePresented(const PresentInfo& present_info) +{ + auto& system = Core::System::GetInstance(); + auto& custom_resource_manager = system.GetCustomResourceManager(); + custom_resource_manager.FramePresented(present_info); + m_camera_manager.FrameUpdate(present_info.frame_count); +} + +void GraphicsModBackend::SetHostConfig(const ShaderHostConfig& config) +{ + m_shader_host_config.bits = config.bits; + + auto& system = Core::System::GetInstance(); + auto& custom_resource_manager = system.GetCustomResourceManager(); + custom_resource_manager.SetHostConfig(config); +} + +void GraphicsModBackend::ClearAdditionalCameras(const MathUtil::Rectangle& rc, + bool color_enable, bool alpha_enable, bool z_enable, + u32 color, u32 z) +{ + m_camera_manager.ClearCameraRenderTargets(rc, color_enable, alpha_enable, z_enable, color, z); +} + +void GraphicsModBackend::CustomDraw(const DrawDataView& draw_data, + VertexManagerBase* vertex_manager, + std::span actions, DrawCallID draw_call) +{ + bool skip = false; + std::optional custom_transform; + GraphicsModSystem::MaterialResource* material_resource = nullptr; + GraphicsModSystem::MeshResource* mesh_resource = nullptr; + bool ignore_mesh_transform = false; + std::optional camera; + GraphicsModActionData::DrawStarted draw_started{draw_data, + VertexLoaderManager::g_current_components, + &skip, + &material_resource, + &mesh_resource, + &ignore_mesh_transform, + &custom_transform, + &camera}; + + static Common::Matrix44 identity = Common::Matrix44::Identity(); + + for (const auto& action : actions) + { + action->OnDrawStarted(&draw_started); + if (camera) + { + m_camera_manager.EnsureCamera(draw_call, *camera); + } + if (mesh_resource) + { + vertex_manager->DrawCustomMesh(mesh_resource, draw_data, custom_transform.value_or(identity), + ignore_mesh_transform, m_camera_manager); + return; + } + } + + if (!skip) + { + vertex_manager->DrawEmulatedMesh(material_resource, draw_data, + custom_transform.value_or(identity), m_camera_manager); + } +} + +DrawCallID GraphicsModBackend::GetSkinnedDrawCallID(DrawCallID draw_call_id, MaterialID material_id, + const DrawDataView& draw_data) +{ + const bool is_draw_gpu_skinned = + IsDrawGPUSkinned(draw_data.vertex_format, draw_data.uid->rasterization_state.primitive); + if (is_draw_gpu_skinned && m_last_draw_gpu_skinned && m_last_material_id == material_id) + { + draw_call_id = m_last_draw_call_id; + } + m_last_draw_call_id = draw_call_id; + m_last_material_id = material_id; + m_last_draw_gpu_skinned = is_draw_gpu_skinned; + + return draw_call_id; +} +} // namespace GraphicsModSystem::Runtime diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModBackend.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModBackend.h new file mode 100644 index 0000000000..9b4ff09092 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModBackend.h @@ -0,0 +1,57 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "Common/CommonTypes.h" +#include "VideoCommon/GraphicsModSystem/Runtime/CameraManager.h" +#include "VideoCommon/GraphicsModSystem/Types.h" +#include "VideoCommon/OpcodeDecoding.h" +#include "VideoCommon/ShaderGenCommon.h" +#include "VideoCommon/VertexManagerBase.h" +#include "VideoCommon/VideoEvents.h" + +class GraphicsModAction; +namespace GraphicsModSystem::Runtime +{ +class GraphicsModBackend +{ +public: + virtual ~GraphicsModBackend() = default; + + virtual void OnDraw(const DrawDataView& draw_started, VertexManagerBase* vertex_manager) = 0; + virtual void OnTextureUnload(TextureType texture_type, std::string_view texture_hash) = 0; + virtual void OnTextureLoad(const TextureView& texture) = 0; + virtual void OnTextureCreate(const TextureView& texture) = 0; + virtual void OnLight() = 0; + virtual void OnFramePresented(const PresentInfo& present_info); + + virtual void AddIndices(OpcodeDecoder::Primitive primitive, u32 num_vertices) = 0; + virtual void ResetIndices() = 0; + + void SetHostConfig(const ShaderHostConfig& config); + + void ClearAdditionalCameras(const MathUtil::Rectangle& rc, bool color_enable, + bool alpha_enable, bool z_enable, u32 color, u32 z); + +protected: + void CustomDraw(const DrawDataView& draw_data, VertexManagerBase* vertex_manager, + std::span actions, DrawCallID draw_call); + + // This will ensure gpu skinned draw calls are joined together + DrawCallID GetSkinnedDrawCallID(DrawCallID draw_call_id, MaterialID material_id, + const DrawDataView& draw_data); + + ShaderHostConfig m_shader_host_config; + + VideoCommon::CameraManager m_camera_manager; + +private: + bool m_last_draw_gpu_skinned = false; + GraphicsModSystem::DrawCallID m_last_draw_call_id = GraphicsModSystem::DrawCallID::INVALID; + GraphicsModSystem::MaterialID m_last_material_id = GraphicsModSystem::MaterialID::INVALID; +}; +} // namespace GraphicsModSystem::Runtime diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModHash.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModHash.cpp new file mode 100644 index 0000000000..eee63f2abf --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModHash.cpp @@ -0,0 +1,80 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModHash.h" + +#include + +#include + +#include "Common/CommonTypes.h" +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModBackend.h" +#include "VideoCommon/RenderState.h" +#include "VideoCommon/XFMemory.h" + +namespace GraphicsModSystem::Runtime +{ +HashOutput GetDrawDataHash(const Config::HashPolicy& hash_policy, const DrawDataView& draw_data) +{ + HashOutput output; + + XXH3_state_t draw_hash_state; + XXH3_INITSTATE(&draw_hash_state); + XXH3_64bits_reset_withSeed(&draw_hash_state, static_cast(1)); + + XXH3_state_t material_hash_state; + XXH3_INITSTATE(&material_hash_state); + XXH3_64bits_reset_withSeed(&material_hash_state, static_cast(1)); + + if ((hash_policy.attributes & Config::HashAttributes::Projection) == + Config::HashAttributes::Projection) + { + XXH3_64bits_update(&draw_hash_state, &xfmem.projection.type, sizeof(ProjectionType)); + } + + if ((hash_policy.attributes & Config::HashAttributes::Blending) == + Config::HashAttributes::Blending) + { + XXH3_64bits_update(&draw_hash_state, &bpmem.blendmode, sizeof(BlendMode)); + } + + if ((hash_policy.attributes & Config::HashAttributes::Indices) == Config::HashAttributes::Indices) + { + XXH3_64bits_update(&draw_hash_state, draw_data.index_data.data(), draw_data.index_data.size()); + } + + if ((hash_policy.attributes & Config::HashAttributes::VertexLayout) == + Config::HashAttributes::VertexLayout) + { + const u32 vertex_count = static_cast(draw_data.vertex_data.size()); + XXH3_64bits_update(&draw_hash_state, &vertex_count, sizeof(u32)); + } + + if (hash_policy.first_texture_only) + { + auto& first_texture = draw_data.textures[0]; + XXH3_64bits_update(&draw_hash_state, first_texture.hash_name.data(), + first_texture.hash_name.size()); + XXH3_64bits_update(&material_hash_state, first_texture.hash_name.data(), + first_texture.hash_name.size()); + } + else + { + std::set texture_hashes; + for (const auto& texture : draw_data.textures) + { + texture_hashes.insert(texture.hash_name); + } + + for (const auto& texture_hash : texture_hashes) + { + XXH3_64bits_update(&draw_hash_state, texture_hash.data(), texture_hash.size()); + XXH3_64bits_update(&material_hash_state, texture_hash.data(), texture_hash.size()); + } + } + + output.draw_call_id = static_cast(XXH3_64bits_digest(&draw_hash_state)); + output.material_id = static_cast(XXH3_64bits_digest(&material_hash_state)); + return output; +} +} // namespace GraphicsModSystem::Runtime diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModHash.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModHash.h new file mode 100644 index 0000000000..60d744e9d3 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModHash.h @@ -0,0 +1,22 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "VideoCommon/GraphicsModSystem/Config/GraphicsModHashPolicy.h" +#include "VideoCommon/GraphicsModSystem/Types.h" + +namespace GraphicsModSystem +{ +struct DrawDataView; +} +namespace GraphicsModSystem::Runtime +{ +struct HashOutput +{ + DrawCallID draw_call_id; + MaterialID material_id; +}; + +HashOutput GetDrawDataHash(const Config::HashPolicy& hash_policy, const DrawDataView& draw_data); +} // namespace GraphicsModSystem::Runtime diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.cpp index 0d070a4310..5490fab921 100644 --- a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.cpp +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.cpp @@ -3,80 +3,16 @@ #include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h" -#include -#include -#include - -#include "Common/Logging/Log.h" -#include "Common/StringUtil.h" -#include "Common/VariantUtil.h" - #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" +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModRuntimeBackend.h" #include "VideoCommon/VideoConfig.h" -std::unique_ptr g_graphics_mod_manager; - -class GraphicsModManager::DecoratedAction final : public GraphicsModAction +namespace GraphicsModSystem::Runtime { -public: - DecoratedAction(std::unique_ptr action, GraphicsModConfig mod) - : m_action_impl(std::move(action)), m_mod(std::move(mod)) - { - } - void OnDrawStarted(GraphicsModActionData::DrawStarted* draw_started) override - { - if (!m_mod.m_enabled) - return; - m_action_impl->OnDrawStarted(draw_started); - } - void OnEFB(GraphicsModActionData::EFB* efb) override - { - if (!m_mod.m_enabled) - return; - m_action_impl->OnEFB(efb); - } - void OnProjection(GraphicsModActionData::Projection* projection) override - { - if (!m_mod.m_enabled) - return; - m_action_impl->OnProjection(projection); - } - void OnProjectionAndTexture(GraphicsModActionData::Projection* projection) override - { - if (!m_mod.m_enabled) - return; - m_action_impl->OnProjectionAndTexture(projection); - } - void OnTextureLoad(GraphicsModActionData::TextureLoad* texture_load) override - { - if (!m_mod.m_enabled) - return; - m_action_impl->OnTextureLoad(texture_load); - } - void OnTextureCreate(GraphicsModActionData::TextureCreate* texture_create) override - { - if (!m_mod.m_enabled) - return; - m_action_impl->OnTextureCreate(texture_create); - } - void OnFrameEnd() override - { - if (!m_mod.m_enabled) - return; - m_action_impl->OnFrameEnd(); - } - -private: - std::unique_ptr m_action_impl; - GraphicsModConfig m_mod; -}; +GraphicsModManager::GraphicsModManager() = default; +GraphicsModManager::~GraphicsModManager() = default; bool GraphicsModManager::Initialize() { @@ -90,281 +26,64 @@ bool GraphicsModManager::Initialize() const u32 old_game_mod_changes = g_ActiveConfig.graphics_mod_config ? g_ActiveConfig.graphics_mod_config->GetChangeCount() : 0; - g_ActiveConfig.graphics_mod_config = GraphicsModGroupConfig(SConfig::GetInstance().GetGameID()); + g_ActiveConfig.graphics_mod_config = + Config::GraphicsModGroup(SConfig::GetInstance().GetGameID()); g_ActiveConfig.graphics_mod_config->Load(); g_ActiveConfig.graphics_mod_config->SetChangeCount(old_game_mod_changes); - g_graphics_mod_manager->Load(*g_ActiveConfig.graphics_mod_config); + Load(*g_ActiveConfig.graphics_mod_config); - m_end_of_frame_event = - AfterFrameEvent::Register([this](Core::System&) { EndOfFrame(); }, "ModManager"); + m_frame_present_event = AfterPresentEvent::Register( + [this](const PresentInfo& present_info) { OnFramePresented(present_info); }, "ModManager"); } return true; } -const std::vector& -GraphicsModManager::GetProjectionActions(ProjectionType projection_type) const +void GraphicsModManager::Load(const Config::GraphicsModGroup& config) { - if (const auto it = m_projection_target_to_actions.find(projection_type); - it != m_projection_target_to_actions.end()) - { - return it->second; - } - - return m_default; + m_backend = std::make_unique(config); } -const std::vector& -GraphicsModManager::GetProjectionTextureActions(ProjectionType projection_type, - const std::string& texture_name) const +const GraphicsModBackend& GraphicsModManager::GetBackend() const { - const auto lookup = fmt::format("{}_{}", texture_name, static_cast(projection_type)); - if (const auto it = m_projection_texture_target_to_actions.find(lookup); - it != m_projection_texture_target_to_actions.end()) - { - return it->second; - } + if (m_editor_backend) + return *m_editor_backend; - return m_default; + return *m_backend; } -const std::vector& -GraphicsModManager::GetDrawStartedActions(const std::string& texture_name) const +GraphicsModBackend& GraphicsModManager::GetBackend() { - if (const auto it = m_draw_started_target_to_actions.find(texture_name); - it != m_draw_started_target_to_actions.end()) - { - return it->second; - } + if (m_editor_backend) + return *m_editor_backend; - return m_default; + return *m_backend; } -const std::vector& -GraphicsModManager::GetTextureLoadActions(const std::string& texture_name) const +void GraphicsModManager::SetEditorBackend(std::unique_ptr editor_backend) { - if (const auto it = m_load_texture_target_to_actions.find(texture_name); - it != m_load_texture_target_to_actions.end()) - { - return it->second; - } - - return m_default; + m_editor_backend = std::move(editor_backend); } -const std::vector& -GraphicsModManager::GetTextureCreateActions(const std::string& texture_name) const +bool GraphicsModManager::IsEditorEnabled() const { - if (const auto it = m_create_texture_target_to_actions.find(texture_name); - it != m_create_texture_target_to_actions.end()) - { - return it->second; - } - - return m_default; + return true; + // return m_editor_backend.get() != nullptr; } -const std::vector& GraphicsModManager::GetEFBActions(const FBInfo& efb) const +void GraphicsModManager::OnFramePresented(const PresentInfo& present_info) { - if (const auto it = m_efb_target_to_actions.find(efb); it != m_efb_target_to_actions.end()) + // Ignore duplicates + if (present_info.reason == PresentInfo::PresentReason::VideoInterfaceDuplicate) + return; + + if (m_editor_backend) { - return it->second; + m_editor_backend->OnFramePresented(present_info); } - - return m_default; -} - -const std::vector& GraphicsModManager::GetXFBActions(const FBInfo& xfb) const -{ - if (const auto it = m_efb_target_to_actions.find(xfb); it != m_efb_target_to_actions.end()) + else { - return it->second; - } - - return m_default; -} - -void GraphicsModManager::Load(const GraphicsModGroupConfig& config) -{ - Reset(); - - const auto& mods = config.GetMods(); - - auto filesystem_library = std::make_shared(); - - std::map> group_to_targets; - for (const auto& mod : mods) - { - for (const GraphicsTargetGroupConfig& group : mod.m_groups) - { - if (m_groups.contains(group.m_name)) - { - WARN_LOG_FMT( - VIDEO, - "Specified graphics mod group '{}' for mod '{}' is already specified by another mod.", - group.m_name, mod.m_title); - } - m_groups.insert(group.m_name); - - const auto internal_group = fmt::format("{}.{}", mod.m_title, group.m_name); - for (const GraphicsTargetConfig& target : group.m_targets) - { - group_to_targets[group.m_name].push_back(target); - 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_asset_id, mod.m_title); - } - else - { - v = std::filesystem::path{base_path} / v; - } - } - - filesystem_library->SetAssetIDMapData(asset.m_asset_id, std::move(asset_map)); - } - } - - for (const auto& mod : mods) - { - for (const GraphicsModFeatureConfig& feature : mod.m_features) - { - const auto create_action = - [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; - } - return std::make_unique(std::move(action), std::move(mod_config)); - }; - - const auto internal_group = fmt::format("{}.{}", mod.m_title, feature.m_group); - - const auto add_target = [&](const GraphicsTargetConfig& target) { - std::visit( - overloaded{ - [&](const DrawStartedTextureTarget& the_target) { - m_draw_started_target_to_actions[the_target.m_texture_info_string].push_back( - m_actions.back().get()); - }, - [&](const LoadTextureTarget& the_target) { - m_load_texture_target_to_actions[the_target.m_texture_info_string].push_back( - m_actions.back().get()); - }, - [&](const CreateTextureTarget& the_target) { - m_create_texture_target_to_actions[the_target.m_texture_info_string].push_back( - m_actions.back().get()); - }, - [&](const EFBTarget& the_target) { - FBInfo info; - info.m_height = the_target.m_height; - info.m_width = the_target.m_width; - info.m_texture_format = the_target.m_texture_format; - m_efb_target_to_actions[info].push_back(m_actions.back().get()); - }, - [&](const XFBTarget& the_target) { - FBInfo info; - info.m_height = the_target.m_height; - info.m_width = the_target.m_width; - info.m_texture_format = the_target.m_texture_format; - m_xfb_target_to_actions[info].push_back(m_actions.back().get()); - }, - [&](const ProjectionTarget& the_target) { - if (the_target.m_texture_info_string) - { - const auto lookup = fmt::format("{}_{}", *the_target.m_texture_info_string, - static_cast(the_target.m_projection_type)); - m_projection_texture_target_to_actions[lookup].push_back( - m_actions.back().get()); - } - else - { - m_projection_target_to_actions[the_target.m_projection_type].push_back( - m_actions.back().get()); - } - }, - }, - target); - }; - - const auto add_action = [&](GraphicsModConfig mod_config) -> bool { - auto action = create_action(feature.m_action, feature.m_action_data, std::move(mod_config)); - if (action == nullptr) - { - WARN_LOG_FMT(VIDEO, "Failed to create action '{}' for group '{}'.", feature.m_action, - feature.m_group); - return false; - } - m_actions.push_back(std::move(action)); - return true; - }; - - // Prefer groups in the pack over groups from another pack - if (const auto local_it = group_to_targets.find(internal_group); - local_it != group_to_targets.end()) - { - if (add_action(mod)) - { - for (const GraphicsTargetConfig& target : local_it->second) - { - add_target(target); - } - } - } - else if (const auto global_it = group_to_targets.find(feature.m_group); - global_it != group_to_targets.end()) - { - if (add_action(mod)) - { - for (const GraphicsTargetConfig& target : global_it->second) - { - add_target(target); - } - } - } - else - { - WARN_LOG_FMT(VIDEO, "Specified graphics mod group '{}' was not found for mod '{}'", - feature.m_group, mod.m_title); - } - } + m_backend->OnFramePresented(present_info); } } - -void GraphicsModManager::EndOfFrame() -{ - for (auto&& action : m_actions) - { - action->OnFrameEnd(); - } -} - -void GraphicsModManager::Reset() -{ - m_actions.clear(); - m_groups.clear(); - m_projection_target_to_actions.clear(); - m_projection_texture_target_to_actions.clear(); - m_draw_started_target_to_actions.clear(); - m_load_texture_target_to_actions.clear(); - m_create_texture_target_to_actions.clear(); - m_efb_target_to_actions.clear(); - m_xfb_target_to_actions.clear(); -} +} // namespace GraphicsModSystem::Runtime diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h index 2d67b4db76..572b1db6b2 100644 --- a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h @@ -3,62 +3,39 @@ #pragma once -#include #include -#include -#include -#include -#include -#include "VideoCommon/GraphicsModSystem/Runtime/FBInfo.h" -#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h" -#include "VideoCommon/TextureInfo.h" -#include "VideoCommon/VideoEvents.h" -#include "VideoCommon/XFMemory.h" +#include "Common/HookableEvent.h" +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModBackend.h" -class GraphicsModGroupConfig; +namespace GraphicsModSystem::Config +{ +class GraphicsModGroup; +} + +namespace GraphicsModSystem::Runtime +{ +class GraphicsModRuntimeBackend; class GraphicsModManager { public: + GraphicsModManager(); + ~GraphicsModManager(); + bool Initialize(); + void Load(const GraphicsModSystem::Config::GraphicsModGroup& config); - const std::vector& GetProjectionActions(ProjectionType projection_type) const; - const std::vector& - GetProjectionTextureActions(ProjectionType projection_type, - const std::string& texture_name) const; - const std::vector& - GetDrawStartedActions(const std::string& texture_name) const; - const std::vector& - GetTextureLoadActions(const std::string& texture_name) const; - const std::vector& - GetTextureCreateActions(const std::string& texture_name) const; - const std::vector& GetEFBActions(const FBInfo& efb) const; - const std::vector& GetXFBActions(const FBInfo& xfb) const; + const GraphicsModBackend& GetBackend() const; + GraphicsModBackend& GetBackend(); - void Load(const GraphicsModGroupConfig& config); + void SetEditorBackend(std::unique_ptr editor_backend); + bool IsEditorEnabled() const; private: - void EndOfFrame(); - void Reset(); + void OnFramePresented(const PresentInfo& present_info); - class DecoratedAction; - - static inline const std::vector m_default = {}; - std::list> m_actions; - std::unordered_map> - m_projection_target_to_actions; - std::unordered_map> - m_projection_texture_target_to_actions; - std::unordered_map> m_draw_started_target_to_actions; - std::unordered_map> m_load_texture_target_to_actions; - std::unordered_map> - m_create_texture_target_to_actions; - std::unordered_map, FBInfoHasher> m_efb_target_to_actions; - std::unordered_map, FBInfoHasher> m_xfb_target_to_actions; - - std::unordered_set m_groups; - - Common::EventHook m_end_of_frame_event; + std::unique_ptr m_backend; + std::unique_ptr m_editor_backend; + Common::EventHook m_frame_present_event; }; - -extern std::unique_ptr g_graphics_mod_manager; +} // namespace GraphicsModSystem::Runtime diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModRuntimeBackend.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModRuntimeBackend.cpp new file mode 100644 index 0000000000..5fdc0055cb --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModRuntimeBackend.cpp @@ -0,0 +1,236 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModRuntimeBackend.h" + +#include +#include + +#include "Common/Logging/Log.h" +#include "Common/VariantUtil.h" + +#include "VideoCommon/Assets/DirectFilesystemAssetLibrary.h" +#include "VideoCommon/GraphicsModSystem/Config/GraphicsMod.h" +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h" +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.h" +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModHash.h" + +namespace GraphicsModSystem::Runtime +{ +GraphicsModRuntimeBackend::GraphicsModRuntimeBackend(const Config::GraphicsModGroup& config_data) +{ + for (auto& config_mod : config_data.GetMods()) + { + if (!config_mod.m_enabled) + continue; + + GraphicsMod runtime_mod; + + runtime_mod.m_hash_policy = config_mod.m_mod.m_default_hash_policy; + + auto filesystem_library = std::make_shared(); + + for (const auto& config_asset : config_mod.m_mod.m_assets) + { + auto asset_map = config_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.", + config_asset.m_asset_id, config_mod.m_id); + } + else + { + v = std::filesystem::path{config_mod.m_path} / v; + } + } + + filesystem_library->SetAssetIDMapData(config_asset.m_asset_id, std::move(asset_map)); + } + + for (const auto& config_action : config_mod.m_mod.m_actions) + { + runtime_mod.m_actions.push_back(GraphicsModActionFactory::Create( + config_action.m_factory_name, config_action.m_data, filesystem_library)); + } + + std::map> tag_to_actions; + for (const auto& [tag_name, action_indexes] : config_mod.m_mod.m_tag_name_to_action_indexes) + { + for (const auto& index : action_indexes) + { + tag_to_actions[tag_name].push_back(runtime_mod.m_actions[index].get()); + } + } + + struct IDWithTags + { + std::variant m_id; + const std::vector* m_tag_names; + }; + std::vector targets; + for (const auto& target : config_mod.m_mod.m_targets) + { + std::visit(overloaded{[&](const Config::IntTarget& int_target) { + const DrawCallID id{int_target.m_target_id}; + runtime_mod.m_draw_id_to_actions.insert_or_assign( + id, std::vector{}); + targets.push_back(IDWithTags{id, &int_target.m_tag_names}); + }, + [&](const Config::StringTarget& str_target) { + const auto id{str_target.m_target_id}; + runtime_mod.m_str_id_to_actions.insert_or_assign( + id, std::vector{}); + targets.push_back(IDWithTags{id, &str_target.m_tag_names}); + }}, + target); + } + + // Handle any specific actions set on targets + for (std::size_t i = 0; i < targets.size(); i++) + { + const auto& target = targets[i]; + std::visit( + overloaded{ + [&](DrawCallID draw_id) { + auto& actions = runtime_mod.m_draw_id_to_actions[draw_id]; + + // First add all specific actions + if (const auto iter = config_mod.m_mod.m_target_index_to_action_indexes.find(i); + iter != config_mod.m_mod.m_target_index_to_action_indexes.end()) + { + for (const auto& action_index : iter->second) + { + actions.push_back(runtime_mod.m_actions[action_index].get()); + } + } + + // Then add all tag actions + for (const auto& tag_name : *target.m_tag_names) + { + auto& tag_actions = tag_to_actions[tag_name]; + actions.insert(actions.end(), tag_actions.begin(), tag_actions.end()); + } + }, + [&](const std::string& str_id) { + auto& actions = runtime_mod.m_str_id_to_actions[str_id]; + + // First add all specific actions + if (const auto iter = config_mod.m_mod.m_target_index_to_action_indexes.find(i); + iter != config_mod.m_mod.m_target_index_to_action_indexes.end()) + { + for (const auto& action_index : iter->second) + { + actions.push_back(runtime_mod.m_actions[action_index].get()); + } + } + + // Then add all tag actions + for (const auto& tag_name : *target.m_tag_names) + { + auto& tag_actions = tag_to_actions[tag_name]; + actions.insert(actions.end(), tag_actions.begin(), tag_actions.end()); + } + }}, + target.m_id); + } + + m_mods.push_back(std::move(runtime_mod)); + } +} + +void GraphicsModRuntimeBackend::OnDraw(const DrawDataView& draw_data, + VertexManagerBase* vertex_manager) +{ + if (m_mods.empty()) + { + vertex_manager->DrawEmulatedMesh(m_camera_manager); + return; + } + + for (auto& mod : m_mods) + { + const auto hash_output = GetDrawDataHash(mod.m_hash_policy, draw_data); + const DrawCallID draw_call_id = + GetSkinnedDrawCallID(hash_output.draw_call_id, hash_output.material_id, draw_data); + + if (const auto iter = mod.m_draw_id_to_actions.find(draw_call_id); + iter != mod.m_draw_id_to_actions.end()) + { + CustomDraw(draw_data, vertex_manager, iter->second, draw_call_id); + return; + } + } + + vertex_manager->DrawEmulatedMesh(m_camera_manager); +} + +void GraphicsModRuntimeBackend::OnTextureLoad(const TextureView& texture) +{ + for (auto& mod : m_mods) + { + if (const auto iter = mod.m_str_id_to_actions.find(texture.hash_name); + iter != mod.m_str_id_to_actions.end()) + { + GraphicsModActionData::TextureLoad load{.texture_name = texture.hash_name}; + for (const auto& action : iter->second) + { + action->OnTextureLoad(&load); + } + break; + } + } +} + +void GraphicsModRuntimeBackend::OnTextureUnload(TextureType, std::string_view) +{ +} + +void GraphicsModRuntimeBackend::OnTextureCreate(const TextureView& texture) +{ + GraphicsModSystem::Runtime::GraphicsModBackend::OnTextureCreate(texture); + + for (auto& mod : m_mods) + { + if (const auto iter = mod.m_str_id_to_actions.find(texture.hash_name); + iter != mod.m_str_id_to_actions.end()) + { + GraphicsModActionData::TextureCreate texture_create{.texture_name = texture.hash_name}; + for (const auto& action : iter->second) + { + action->OnTextureCreate(&texture_create); + } + break; + } + } +} + +void GraphicsModRuntimeBackend::OnLight() +{ +} + +void GraphicsModRuntimeBackend::OnFramePresented(const PresentInfo& present_info) +{ + GraphicsModSystem::Runtime::GraphicsModBackend::OnFramePresented(present_info); + + for (auto& mod : m_mods) + { + for (const auto& action : mod.m_actions) + { + action->OnFrameEnd(); + } + } +} + +void GraphicsModRuntimeBackend::AddIndices(OpcodeDecoder::Primitive, u32) +{ +} + +void GraphicsModRuntimeBackend::ResetIndices() +{ +} + +} // namespace GraphicsModSystem::Runtime diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModRuntimeBackend.h b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModRuntimeBackend.h new file mode 100644 index 0000000000..c44d308dc7 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModRuntimeBackend.h @@ -0,0 +1,72 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include + +#include "Common/CommonTypes.h" + +#include "VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.h" +#include "VideoCommon/GraphicsModSystem/Config/GraphicsModHashPolicy.h" +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h" +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModBackend.h" +#include "VideoCommon/GraphicsModSystem/Types.h" + +namespace GraphicsModSystem::Runtime +{ +class GraphicsModRuntimeBackend final : public GraphicsModBackend +{ +public: + explicit GraphicsModRuntimeBackend(const Config::GraphicsModGroup& config_data); + void OnDraw(const DrawDataView& draw_data, VertexManagerBase* vertex_manager) override; + void OnTextureLoad(const TextureView& texture) override; + void OnTextureUnload(TextureType texture_type, std::string_view texture_hash) override; + void OnTextureCreate(const TextureView& texture) override; + void OnLight() override; + void OnFramePresented(const PresentInfo& present_info) override; + + void AddIndices(OpcodeDecoder::Primitive primitive, u32 num_vertices) override; + void ResetIndices() override; + +private: + struct GraphicsMod + { + GraphicsMod() = default; + ~GraphicsMod() = default; + GraphicsMod(GraphicsMod&) = delete; + GraphicsMod(GraphicsMod&&) noexcept = default; + GraphicsMod& operator=(GraphicsMod&) = delete; + GraphicsMod& operator=(GraphicsMod&&) noexcept = default; + + Config::HashPolicy m_hash_policy; + std::unordered_map> m_draw_id_to_actions; + + struct string_hash + { + using is_transparent = void; + [[nodiscard]] size_t operator()(const char* txt) const + { + return std::hash{}(txt); + } + [[nodiscard]] size_t operator()(std::string_view txt) const + { + return std::hash{}(txt); + } + [[nodiscard]] size_t operator()(const std::string& txt) const + { + return std::hash{}(txt); + } + }; + + std::unordered_map, string_hash, std::equal_to<>> + m_str_id_to_actions; + std::vector> m_actions; + }; + std::vector m_mods; +}; +} // namespace GraphicsModSystem::Runtime diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Types.h b/Source/Core/VideoCommon/GraphicsModSystem/Types.h new file mode 100644 index 0000000000..e039d16390 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModSystem/Types.h @@ -0,0 +1,174 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "Common/CommonTypes.h" +#include "Common/Matrix.h" +#include "Common/SmallVector.h" +#include "VideoCommon/AbstractFramebuffer.h" +#include "VideoCommon/AbstractPipeline.h" +#include "VideoCommon/AbstractTexture.h" +#include "VideoCommon/ConstantManager.h" +#include "VideoCommon/Constants.h" +#include "VideoCommon/GXPipelineTypes.h" +#include "VideoCommon/NativeVertexFormat.h" +#include "VideoCommon/RenderState.h" +#include "VideoCommon/XFMemory.h" + +namespace GraphicsModSystem +{ +enum class DrawCallID : unsigned long long +{ + INVALID = 0 +}; + +enum class MeshID : unsigned long long +{ + INVALID = 0 +}; + +enum class MaterialID : unsigned long long +{ + INVALID = 0 +}; + +enum class LightID : unsigned long long +{ + INVALID = 0 +}; + +using TextureCacheID = std::string; +using TextureCacheIDView = std::string_view; + +enum TextureType +{ + Normal, + EFB, + XFB +}; + +struct TextureView +{ + TextureType texture_type = TextureType::Normal; + AbstractTexture* texture_data = nullptr; + std::string_view hash_name; + u8 unit = 0; +}; + +struct Texture +{ + TextureType texture_type = TextureType::Normal; + std::string hash_name; + u8 unit = 0; +}; + +struct DrawDataView +{ + std::span vertex_data; + std::span index_data; + std::span gpu_skinning_position_transform; + std::span gpu_skinning_normal_transform; + NativeVertexFormat* vertex_format = nullptr; + Common::SmallVector textures; + std::array samplers; + + ProjectionType projection_type; + VideoCommon::GXPipelineUid* uid; +}; + +struct DrawData +{ + Common::SmallVector textures; + std::array samplers; + + std::size_t vertex_count = 0; + std::size_t index_count = 0; + + ProjectionType projection_type; + Projection::Raw projection_mat; + RasterizationState rasterization_state; + DepthState depth_state; + BlendingState blending_state; + u64 xfb_counter = 0; +}; + +enum CameraType +{ + None = 0, + Closest = 1, + Specify = 2 +}; + +struct TextureResource +{ + const AbstractTexture* texture = nullptr; + const SamplerState* sampler = nullptr; + u32 sampler_index; + std::string_view texture_hash_for_sampler; +}; + +struct InputRenderTargetResource +{ + const SamplerState* sampler = nullptr; + u32 sampler_index; + std::string_view texture_hash_for_sampler; + + std::string_view render_target_name; + CameraType camera_type = None; + DrawCallID camera_originating_draw_call; +}; + +struct OutputRenderTargetResource +{ + AbstractTextureFormat format; + std::string_view name; +}; + +struct MaterialResource +{ + const AbstractPipeline* pipeline; + std::span pixel_uniform_data; + std::span vertex_uniform_data; + + using TextureLikeResource = std::variant; + Common::SmallVector textures; + Common::SmallVector render_targets; + + MaterialResource* next = nullptr; +}; + +struct MeshChunkResource +{ + MaterialResource* material; + std::span vertex_data; + std::span index_data; + u32 vertex_stride; + NativeVertexFormat* vertex_format; + PrimitiveType primitive_type; + u32 components_available; + Common::Matrix44 transform; +}; + +struct MeshResource +{ + std::vector mesh_chunks; + Common::Vec3 pivot_point; +}; + +struct Camera +{ + bool generates_render_targets = false; + AbstractTextureType render_target_type = AbstractTextureType::Texture_2D; + Common::Matrix44 transform = Common::Matrix44::Identity(); + float resolution_multiplier = 1.0f; + bool skip_orthographic = true; +}; +} // namespace GraphicsModSystem diff --git a/Source/Core/VideoCommon/HiresTextures.cpp b/Source/Core/VideoCommon/HiresTextures.cpp index b64c521e08..044995da85 100644 --- a/Source/Core/VideoCommon/HiresTextures.cpp +++ b/Source/Core/VideoCommon/HiresTextures.cpp @@ -25,18 +25,18 @@ #include "Core/ConfigManager.h" #include "Core/System.h" #include "VideoCommon/Assets/CustomAsset.h" -#include "VideoCommon/Assets/CustomAssetLoader.h" #include "VideoCommon/Assets/DirectFilesystemAssetLibrary.h" +#include "VideoCommon/GraphicsModSystem/Runtime/CustomResourceManager.h" #include "VideoCommon/OnScreenDisplay.h" #include "VideoCommon/VideoConfig.h" +static auto s_file_library = std::make_shared(); + constexpr std::string_view s_format_prefix{"tex1_"}; static std::unordered_map> s_hires_texture_cache; static std::unordered_map s_hires_texture_id_to_arbmipmap; -static auto s_file_library = std::make_shared(); - namespace { std::pair GetNameArbPair(const TextureInfo& texture_info) @@ -95,10 +95,11 @@ void HiresTexture::Update() GetTextureDirectoriesWithGameId(File::GetUserPath(D_HIRESTEXTURES_IDX), game_id); const std::vector extensions{".png", ".dds"}; - auto& system = Core::System::GetInstance(); - for (const auto& texture_directory : texture_directories) { + // Watch this directory for any texture reloads + s_file_library->Watch(texture_directory); + const auto texture_paths = Common::DoFileSearch({texture_directory}, extensions, /*recursive*/ true); @@ -130,10 +131,10 @@ void HiresTexture::Update() if (g_ActiveConfig.bCacheHiresTextures) { - auto hires_texture = std::make_shared( - has_arbitrary_mipmaps, - system.GetCustomAssetLoader().LoadGameTexture(filename, s_file_library)); - s_hires_texture_cache.try_emplace(filename, std::move(hires_texture)); + auto hires_texture = + std::make_shared(has_arbitrary_mipmaps, std::move(filename)); + static_cast(hires_texture->LoadTexture()); + s_hires_texture_cache.try_emplace(hires_texture->GetId(), hires_texture); } } } @@ -167,7 +168,7 @@ void HiresTexture::Clear() std::shared_ptr HiresTexture::Search(const TextureInfo& texture_info) { - const auto [base_filename, has_arb_mipmaps] = GetNameArbPair(texture_info); + auto [base_filename, has_arb_mipmaps] = GetNameArbPair(texture_info); if (base_filename == "") return nullptr; @@ -177,24 +178,27 @@ std::shared_ptr HiresTexture::Search(const TextureInfo& texture_in } else { - auto& system = Core::System::GetInstance(); - auto hires_texture = std::make_shared( - has_arb_mipmaps, - system.GetCustomAssetLoader().LoadGameTexture(base_filename, s_file_library)); + auto hires_texture = std::make_shared(has_arb_mipmaps, std::move(base_filename)); if (g_ActiveConfig.bCacheHiresTextures) { - s_hires_texture_cache.try_emplace(base_filename, hires_texture); + s_hires_texture_cache.try_emplace(hires_texture->GetId(), hires_texture); } return hires_texture; } } -HiresTexture::HiresTexture(bool has_arbitrary_mipmaps, - std::shared_ptr asset) - : m_has_arbitrary_mipmaps(has_arbitrary_mipmaps), m_game_texture(std::move(asset)) +HiresTexture::HiresTexture(bool has_arbitrary_mipmaps, std::string id) + : m_has_arbitrary_mipmaps(has_arbitrary_mipmaps), m_id(std::move(id)) { } +VideoCommon::CustomTextureData* HiresTexture::LoadTexture() const +{ + auto& system = Core::System::GetInstance(); + auto& custom_resource_manager = system.GetCustomResourceManager(); + return custom_resource_manager.GetTextureDataFromAsset(m_id, s_file_library); +} + std::set GetTextureDirectoriesWithGameId(const std::string& root_directory, const std::string& game_id) { diff --git a/Source/Core/VideoCommon/HiresTextures.h b/Source/Core/VideoCommon/HiresTextures.h index 5312285dd0..ceb5b3ce2b 100644 --- a/Source/Core/VideoCommon/HiresTextures.h +++ b/Source/Core/VideoCommon/HiresTextures.h @@ -10,7 +10,6 @@ #include "Common/CommonTypes.h" #include "VideoCommon/Assets/CustomTextureData.h" -#include "VideoCommon/Assets/TextureAsset.h" #include "VideoCommon/TextureConfig.h" #include "VideoCommon/TextureInfo.h" @@ -27,12 +26,13 @@ public: static void Shutdown(); static std::shared_ptr Search(const TextureInfo& texture_info); - HiresTexture(bool has_arbitrary_mipmaps, std::shared_ptr asset); + HiresTexture(bool has_arbitrary_mipmaps, std::string id); bool HasArbitraryMipmaps() const { return m_has_arbitrary_mipmaps; } - const std::shared_ptr& GetAsset() const { return m_game_texture; } + VideoCommon::CustomTextureData* LoadTexture() const; + const std::string& GetId() const { return m_id; } private: bool m_has_arbitrary_mipmaps = false; - std::shared_ptr m_game_texture; + std::string m_id; }; diff --git a/Source/Core/VideoCommon/IndexGenerator.cpp b/Source/Core/VideoCommon/IndexGenerator.cpp index 91db420950..dab228d23f 100644 --- a/Source/Core/VideoCommon/IndexGenerator.cpp +++ b/Source/Core/VideoCommon/IndexGenerator.cpp @@ -262,11 +262,14 @@ u16* AddPoints_VSExpand(u16* index_ptr, u32 num_verts, u32 index) } } // Anonymous namespace -void IndexGenerator::Init() +void IndexGenerator::Init(bool editor_enabled) { using OpcodeDecoder::Primitive; - if (g_backend_info.bSupportsPrimitiveRestart) + // When editor is enabled, we can't take shortcuts + // as we might want to save meshes to a file + // Some formats don't allow primitive restart + if (g_backend_info.bSupportsPrimitiveRestart && !editor_enabled) { m_primitive_table[Primitive::GX_DRAW_QUADS] = AddQuads; m_primitive_table[Primitive::GX_DRAW_QUADS_2] = AddQuads_nonstandard; @@ -282,7 +285,7 @@ void IndexGenerator::Init() m_primitive_table[Primitive::GX_DRAW_TRIANGLE_STRIP] = AddStrip; m_primitive_table[Primitive::GX_DRAW_TRIANGLE_FAN] = AddFan; } - if (g_Config.UseVSForLinePointExpand()) + if (g_Config.UseVSForLinePointExpand() && !editor_enabled) { if (g_backend_info.bSupportsPrimitiveRestart) { diff --git a/Source/Core/VideoCommon/IndexGenerator.h b/Source/Core/VideoCommon/IndexGenerator.h index 3c57ea7803..2768af512a 100644 --- a/Source/Core/VideoCommon/IndexGenerator.h +++ b/Source/Core/VideoCommon/IndexGenerator.h @@ -13,7 +13,7 @@ class IndexGenerator { public: - void Init(); + void Init(bool editor_enabled); void Start(u16* index_ptr); void AddIndices(OpcodeDecoder::Primitive primitive, u32 num_vertices); @@ -24,6 +24,7 @@ public: u32 GetNumVerts() const { return m_base_index; } u32 GetIndexLen() const { return static_cast(m_index_buffer_current - m_base_index_ptr); } u32 GetRemainingIndices(OpcodeDecoder::Primitive primitive) const; + u16* GetIndexDataStart() const { return m_base_index_ptr; } private: u16* m_index_buffer_current = nullptr; diff --git a/Source/Core/VideoCommon/LightingShaderGen.cpp b/Source/Core/VideoCommon/LightingShaderGen.cpp index 4fb2c98ebd..ea67ecdea9 100644 --- a/Source/Core/VideoCommon/LightingShaderGen.cpp +++ b/Source/Core/VideoCommon/LightingShaderGen.cpp @@ -150,6 +150,80 @@ void GenerateLightingShaderCode(ShaderCode& object, const LightingUidData& uid_d } } +void GenerateLightingShaderHeader(ShaderCode& object, const LightingUidData& uid_data) +{ + for (u32 j = 0; j < NUM_XF_COLOR_CHANNELS; j++) + { + object.Write( + "vec4 dolphin_calculate_lighting_chn{}(vec4 vertex_color, vec4 pos, vec3 _normal)\n", j); + object.Write("{{\n"); + + object.Write("\tint4 lacc;\n" + "\tfloat3 ldir, h, cosAttn, distAttn;\n" + "\tfloat dist, dist2, attn;\n"); + + const bool colormatsource = !!(uid_data.matsource & (1 << j)); + if (colormatsource) // from vertex + object.Write("\tint4 mat = int4(round(vertex_color * 255.0));\n"); + else // from color + object.Write("\tint4 mat = {}[{}];\n", I_MATERIALS, j + 2); + + if ((uid_data.enablelighting & (1 << j)) != 0) + { + if ((uid_data.ambsource & (1 << j)) != 0) // from vertex + object.Write("\tlacc = int4(round(vertex_color * 255.0));\n"); + else // from color + object.Write("\tlacc = {}[{}];\n", I_MATERIALS, j); + } + else + { + object.Write("\tlacc = int4(255, 255, 255, 255);\n"); + } + + // check if alpha is different + const bool alphamatsource = !!(uid_data.matsource & (1 << (j + 2))); + if (alphamatsource != colormatsource) + { + if (alphamatsource) // from vertex + object.Write("\tmat.w = int(round(vertex_color.w * 255.0));\n"); + else // from color + object.Write("\tmat.w = {}[{}].w;\n", I_MATERIALS, j + 2); + } + + if ((uid_data.enablelighting & (1 << (j + 2))) != 0) + { + if ((uid_data.ambsource & (1 << (j + 2))) != 0) // from vertex + object.Write("\tlacc.w = int(round(vertex_color.w * 255.0));\n"); + else // from color + object.Write("\tlacc.w = {}[{}].w;\n", I_MATERIALS, j); + } + else + { + object.Write("\tlacc.w = 255;\n"); + } + + 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) + GenerateLightShader(object, uid_data, i, j, false); + } + } + 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) + GenerateLightShader(object, uid_data, i, j + 2, true); + } + } + object.Write("\tlacc = clamp(lacc, 0, 255);\n"); + object.Write("\treturn vec4((mat * (lacc + (lacc >> 7))) >> 8) / 255.0;\n"); + object.Write("}}\n\n"); + } +} + void GetLightingShaderUid(LightingUidData& uid_data) { for (u32 j = 0; j < NUM_XF_COLOR_CHANNELS; j++) @@ -353,3 +427,142 @@ void GenerateCustomLightingImplementation(ShaderCode* out, const LightingUidData out->Write("\tcustom_data.light_chan{}_alpha_count = {};\n", j, light_count); } } + +static void GenerateLightingImpl(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\tfrag_input.{}[{}].direction = " LIGHT_DIR ".xyz;\n", name, custom_light_index, + LIGHT_DIR_PARAMS(index)); + out->Write("\t\tfrag_input.{}[{}].position = " LIGHT_POS ".xyz;\n", name, custom_light_index, + LIGHT_POS_PARAMS(index)); + out->Write("\t\tfrag_input.{}[{}].cosatt = " LIGHT_COSATT ";\n", name, custom_light_index, + LIGHT_COSATT_PARAMS(index)); + out->Write("\t\tfrag_input.{}[{}].distatt = " LIGHT_DISTATT ";\n", name, custom_light_index, + LIGHT_DISTATT_PARAMS(index)); + out->Write("\t\tfrag_input.{}[{}].attenuation_type = {};\n", name, custom_light_index, + static_cast(attnfunc)); + if (alpha) + { + out->Write("\t\tfrag_input.{}[{}].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\tfrag_input.{}[{}].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"); +} + +void GenerateCustomLighting(ShaderCode* out, const LightingUidData& uid_data) +{ + for (u32 i = 0; i < 8; i++) + { + for (u32 channel_index = 0; channel_index < NUM_XF_COLOR_CHANNELS; channel_index++) + { + out->Write("\tfrag_input.lights_chan{}_color[{}].direction = float3(0, 0, 0);\n", + channel_index, i); + out->Write("\tfrag_input.lights_chan{}_color[{}].position = float3(0, 0, 0);\n", + channel_index, i); + out->Write("\tfrag_input.lights_chan{}_color[{}].color = float3(0, 0, 0);\n", channel_index, + i); + out->Write("\tfrag_input.lights_chan{}_color[{}].cosatt = float4(0, 0, 0, 0);\n", + channel_index, i); + out->Write("\tfrag_input.lights_chan{}_color[{}].distatt = float4(0, 0, 0, 0);\n", + channel_index, i); + out->Write("\tfrag_input.lights_chan{}_color[{}].attenuation_type = 0;\n", channel_index, i); + + out->Write("\tfrag_input.lights_chan{}_alpha[{}].direction = float3(0, 0, 0);\n", + channel_index, i); + out->Write("\tfrag_input.lights_chan{}_alpha[{}].position = float3(0, 0, 0);\n", + channel_index, i); + out->Write("\tfrag_input.lights_chan{}_alpha[{}].color = float3(0, 0, 0);\n", channel_index, + i); + out->Write("\tfrag_input.lights_chan{}_alpha[{}].cosatt = float4(0, 0, 0, 0);\n", + channel_index, i); + out->Write("\tfrag_input.lights_chan{}_alpha[{}].distatt = float4(0, 0, 0, 0);\n", + channel_index, i); + out->Write("\tfrag_input.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("frag_input.base_material[{}] = frag_input.color_{};\n", j, j); + else // from color + out->Write("frag_input.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("frag_input.ambient_lighting[{}] = frag_input.color_{};\n", j, j); + else // from color + out->Write("frag_input.ambient_lighting[{}] = {}[{}] / 255.0;\n", j, I_MATERIALS, j); + } + else + { + out->Write("frag_input.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("frag_input.base_material[{}].w = frag_input.color_{}.w;\n", j, j); + else // from color + out->Write("frag_input.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("frag_input.ambient_lighting[{}].w = frag_input.color_{}.w;\n", j, j); + else // from color + out->Write("frag_input.ambient_lighting[{}].w = {}[{}].w / 255.0;\n", j, I_MATERIALS, j); + } + else + { + out->Write("frag_input.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) + { + GenerateLightingImpl(out, uid_data, i, j, j, light_count, false); + light_count++; + } + } + } + out->Write("\tfrag_input.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) + { + GenerateLightingImpl(out, uid_data, i, j + 2, j, light_count, true); + light_count++; + } + } + } + out->Write("\tfrag_input.light_chan{}_alpha_count = {};\n", j, light_count); + } +} diff --git a/Source/Core/VideoCommon/LightingShaderGen.h b/Source/Core/VideoCommon/LightingShaderGen.h index b06ec40c4a..2ae65422c1 100644 --- a/Source/Core/VideoCommon/LightingShaderGen.h +++ b/Source/Core/VideoCommon/LightingShaderGen.h @@ -46,8 +46,10 @@ 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 GenerateLightingShaderHeader(ShaderCode& object, const LightingUidData& uid_data); 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); +void GenerateCustomLighting(ShaderCode* out, const LightingUidData& uid_data); diff --git a/Source/Core/VideoCommon/OnScreenUI.cpp b/Source/Core/VideoCommon/OnScreenUI.cpp index d1c15f7f80..609d5fad5f 100644 --- a/Source/Core/VideoCommon/OnScreenUI.cpp +++ b/Source/Core/VideoCommon/OnScreenUI.cpp @@ -18,6 +18,7 @@ #include "VideoCommon/AbstractPipeline.h" #include "VideoCommon/AbstractShader.h" #include "VideoCommon/FramebufferShaderGen.h" +#include "VideoCommon/GraphicsModEditor/EditorMain.h" #include "VideoCommon/NetPlayChatUI.h" #include "VideoCommon/NetPlayGolfUI.h" #include "VideoCommon/OnScreenDisplay.h" @@ -103,11 +104,19 @@ bool OnScreenUI::Initialize(u32 width, u32 height, float scale) m_ready = true; BeginImGuiFrameUnlocked(width, height); // lock is already held + auto& system = Core::System::GetInstance(); + auto& graphics_mod_editor = system.GetGraphicsModEditor(); + graphics_mod_editor.Initialize(); + return true; } OnScreenUI::~OnScreenUI() { + auto& system = Core::System::GetInstance(); + auto& graphics_mod_editor = system.GetGraphicsModEditor(); + graphics_mod_editor.Shutdown(); + std::unique_lock imgui_lock(m_imgui_mutex); ImGui::EndFrame(); @@ -312,6 +321,13 @@ void OnScreenUI::DrawDebugText() ImGui::End(); } + auto& system = Core::System::GetInstance(); + auto& graphics_mod_editor = system.GetGraphicsModEditor(); + if (graphics_mod_editor.IsEnabled()) + { + graphics_mod_editor.DrawImGui(); + } + if (g_ActiveConfig.bOverlayStats) g_stats.Display(); @@ -437,8 +453,8 @@ void OnScreenUI::SetKeyMap(const DolphinKeyMap& key_map) ImGuiKey_DownArrow, ImGuiKey_PageUp, ImGuiKey_PageDown, ImGuiKey_Home, ImGuiKey_End, ImGuiKey_Insert, ImGuiKey_Delete, ImGuiKey_Backspace, ImGuiKey_Space, ImGuiKey_Enter, ImGuiKey_Escape, ImGuiKey_KeypadEnter, - ImGuiKey_A, ImGuiKey_C, ImGuiKey_V, ImGuiKey_X, - ImGuiKey_Y, ImGuiKey_Z, + ImGuiMod_Ctrl, ImGuiMod_Shift, ImGuiKey_A, ImGuiKey_C, + ImGuiKey_V, ImGuiKey_X, ImGuiKey_Y, ImGuiKey_Z, }; auto lock = GetImGuiLock(); diff --git a/Source/Core/VideoCommon/OnScreenUIKeyMap.h b/Source/Core/VideoCommon/OnScreenUIKeyMap.h index c637f3938b..6e644fef71 100644 --- a/Source/Core/VideoCommon/OnScreenUIKeyMap.h +++ b/Source/Core/VideoCommon/OnScreenUIKeyMap.h @@ -26,6 +26,8 @@ enum class DolphinKey Enter, Escape, KeyPadEnter, + CTRL, + SHIFT, A, // for text edit CTRL+A: select all C, // for text edit CTRL+C: copy V, // for text edit CTRL+V: paste diff --git a/Source/Core/VideoCommon/PixelShaderGen.cpp b/Source/Core/VideoCommon/PixelShaderGen.cpp index 9fafd630b9..40ee395f94 100644 --- a/Source/Core/VideoCommon/PixelShaderGen.cpp +++ b/Source/Core/VideoCommon/PixelShaderGen.cpp @@ -902,6 +902,7 @@ ShaderCode GeneratePixelShaderCode(APIType api_type, const ShaderHostConfig& hos const pixel_shader_uid_data* uid_data, const CustomPixelShaderContents& custom_details) { + // return PixelShader::WriteFullShader(api_type, host_config, uid_data, "", ""); ShaderCode out; const bool per_pixel_lighting = g_ActiveConfig.bEnablePixelLighting; @@ -2183,3 +2184,862 @@ static void WriteBlend(ShaderCode& out, const pixel_shader_uid_data* uid_data) out.Write("\treal_ocol0 = blend_result;\n"); } + +namespace PixelShader +{ +void WriteDitherHeader(APIType api_type, const ShaderHostConfig& host_config, + const pixel_shader_uid_data* uid_data, ShaderCode& out) +{ + if (uid_data->dither) + { + out.Write("ivec3 dolphin_calculate_dither(ivec4 prev, ivec4 pos)\n"); + out.Write("{{\n"); + // Flipper uses a standard 2x2 Bayer Matrix for 6 bit dithering + // Here the matrix is encoded into the two factor constants + out.Write("\tint2 dither = int2(pos.xy) & 1;\n"); + out.Write("\treturn (prev.rgb - (prev.rgb >> 6)) + abs(dither.y * 3 - dither.x * 2);\n"); + out.Write("}}\n\n"); + } +} + +void WriteFogHeader(APIType api_type, const ShaderHostConfig& host_config, + const pixel_shader_uid_data* uid_data, ShaderCode& out) +{ + out.Write("ivec3 dolphin_calculate_fog(ivec4 color, vec4 pos, int zCoord)\n"); + out.Write("{{\n"); + if (uid_data->fog_fsel == FogType::Off) + { + out.Write("\treturn color.rgb;\n"); + out.Write("}}\n\n"); + return; // no Fog + } + + out.SetConstantsUsed(C_FOGCOLOR, C_FOGCOLOR); + out.SetConstantsUsed(C_FOGI, C_FOGI); + out.SetConstantsUsed(C_FOGF, C_FOGF + 1); + if (uid_data->fog_proj == FogProjection::Perspective) + { + // perspective + // ze = A/(B - (Zs >> B_SHF) + // TODO: Verify that we want to drop lower bits here! (currently taken over from software + // renderer) + // Maybe we want to use "ze = (A << B_SHF)/((B << B_SHF) - Zs)" instead? + // That's equivalent, but keeps the lower bits of Zs. + out.Write("\tfloat ze = (" I_FOGF ".x * 16777216.0) / float(" I_FOGI ".y - (zCoord >> " I_FOGI + ".w));\n"); + } + else + { + // orthographic + // ze = a*Zs (here, no B_SHF) + out.Write("\tfloat ze = " I_FOGF ".x * float(zCoord) / 16777216.0;\n"); + } + + // x_adjust = sqrt((x-center)^2 + k^2)/k + // ze *= x_adjust + if (uid_data->fog_RangeBaseEnabled) + { + out.SetConstantsUsed(C_FOGF, C_FOGF); + out.Write("\tfloat offset = (2.0 * (pos.x / " I_FOGF ".w)) - 1.0 - " I_FOGF ".z;\n" + "\tfloat floatindex = clamp(9.0 - abs(offset) * 9.0, 0.0, 9.0);\n" + "\tuint indexlower = uint(floatindex);\n" + "\tuint indexupper = indexlower + 1u;\n" + "\tfloat klower = " I_FOGRANGE "[indexlower >> 2u][indexlower & 3u];\n" + "\tfloat kupper = " I_FOGRANGE "[indexupper >> 2u][indexupper & 3u];\n" + "\tfloat k = lerp(klower, kupper, frac(floatindex));\n" + "\tfloat x_adjust = sqrt(offset * offset + k * k) / k;\n" + "\tze *= x_adjust;\n"); + } + + out.Write("\tfloat fog = clamp(ze - " I_FOGF ".y, 0.0, 1.0);\n"); + + if (uid_data->fog_fsel >= FogType::Exp) + { + out.Write("{}", tev_fog_funcs_table[uid_data->fog_fsel]); + } + else + { + if (uid_data->fog_fsel != FogType::Linear) + WARN_LOG_FMT(VIDEO, "Unknown Fog Type! {}", uid_data->fog_fsel); + } + + out.Write("\tint ifog = iround(fog * 256.0);\n"); + out.Write("\treturn (color.rgb * (256 - ifog) + " I_FOGCOLOR ".rgb * ifog) >> 8;\n"); + out.Write("}}\n\n"); +} + +void WriteDepthHeader(APIType api_type, const ShaderHostConfig& host_config, + const pixel_shader_uid_data* uid_data, ShaderCode& out) +{ + out.Write("int dolphin_calculate_zcoord(vec4 rawpos, vec4 clipPos, ivec4 last_stage_texmap)\n"); + out.Write("{{\n"); + + if (uid_data->zfreeze) + { + out.SetConstantsUsed(C_ZSLOPE, C_ZSLOPE); + out.SetConstantsUsed(C_EFBSCALE, C_EFBSCALE); + + out.Write("\tvec2 screenpos = rawpos.xy * " I_EFBSCALE ".xy;\n"); + + // Opengl has reversed vertical screenspace coordinates + if (api_type == APIType::OpenGL) + out.Write("\tscreenpos.y = {}.0 - screenpos.y;\n", EFB_HEIGHT); + + out.Write("\tint zCoord = int(" I_ZSLOPE ".z + " I_ZSLOPE ".x * screenpos.x + " I_ZSLOPE + ".y * screenpos.y);\n"); + } + else if (!host_config.fast_depth_calc) + { + // FastDepth means to trust the depth generated in perspective division. + // It should be correct, but it seems not to be as accurate as required. TODO: Find out why! + // For disabled FastDepth we just calculate the depth value again. + // The performance impact of this additional calculation doesn't matter, but it prevents + // the host GPU driver from performing any early depth test optimizations. + out.SetConstantsUsed(C_ZBIAS + 1, C_ZBIAS + 1); + // the screen space depth value = far z + (clip z / clip w) * z range + out.Write("\tint zCoord = " I_ZBIAS "[1].x + int((clipPos.z / clipPos.w) * float(" I_ZBIAS + "[1].y));\n"); + } + else + { + if (!host_config.backend_reversed_depth_range) + out.Write("\tint zCoord = int((1.0 - rawpos.z) * 16777216.0);\n"); + else + out.Write("\tint zCoord = int(rawpos.z * 16777216.0);\n"); + } + out.Write("\tzCoord = clamp(zCoord, 0, 0xFFFFFF);\n"); + + // depth texture can safely be ignored if the result won't be written to the depth buffer + // (early_ztest) and isn't used for fog either + const bool skip_ztexture = !uid_data->per_pixel_depth && uid_data->fog_fsel == FogType::Off; + + // Note: depth texture output is only written to depth buffer if late depth test is used + // theoretical final depth value is used for fog calculation, though, so we have to emulate + // ztextures anyway + if (uid_data->ztex_op != ZTexOp::Disabled && !skip_ztexture) + { + // use the texture input of the last texture stage, hopefully this has been read and + // is in correct format... + out.SetConstantsUsed(C_ZBIAS, C_ZBIAS + 1); + out.Write("\tzCoord = idot(" I_ZBIAS "[0].xyzw, last_stage_texmap.xyzw) + " I_ZBIAS + "[1].w {};\n", + (uid_data->ztex_op == ZTexOp::Add) ? "+ zCoord" : ""); + out.Write("\tzCoord = zCoord & 0xFFFFFF;\n"); + } + + out.Write("\treturn zCoord;\n"); + + out.Write("}}\n\n"); +} + +void WriteLogicOpHeader(APIType api_type, const ShaderHostConfig& host_config, + const pixel_shader_uid_data* uid_data, ShaderCode& out) +{ + static constexpr std::array logic_op_mode{ + "ivec4(0, 0, 0, 0)", // CLEAR + "prev & fb_value", // AND + "prev & ~fb_value", // AND_REVERSE + "prev", // COPY + "~prev & fb_value", // AND_INVERTED + "fb_value", // NOOP + "prev ^ fb_value", // XOR + "prev | fb_value", // OR + "~(prev | fb_value)", // NOR + "~(prev ^ fb_value)", // EQUIV + "~fb_value", // INVERT + "prev | ~fb_value", // OR_REVERSE + "~prev", // COPY_INVERTED + "~prev | fb_value", // OR_INVERTED + "~(prev & fb_value)", // NAND + "ivec4(255, 255, 255, 255)", // SET + }; + + out.Write("ivec4 dolphin_calculate_logicop(vec4 color, vec4 prev)\n"); + out.Write("{{\n"); + + out.Write("\tivec4 fb_value = iround(color * 255.0);\n"); + out.Write("\treturn ({}) & 0xff;\n", logic_op_mode[uid_data->logic_op_mode]); + out.Write("}}\n\n"); +} + +void WriteColorHeader(APIType api_type, const ShaderHostConfig& host_config, + const pixel_shader_uid_data* uid_data, ShaderCode& out) +{ + if (uid_data->uint_output) + { + out.Write("uvec4 dolphin_calculate_final_color0(ivec4 prev)\n"); + } + else + { + out.Write("vec4 dolphin_calculate_final_color0(ivec4 prev)\n"); + } + out.Write("{{\n"); + + // Some backends require the shader outputs be uint when writing to a uint render target for logic + // op. + if (uid_data->uint_output) + { + if (uid_data->rgba6_format) + out.Write("\treturn uint4(prev & 0xFC);\n"); + else + out.Write("\treturn uint4(prev);\n"); + } + else + { + out.Write("\tvec4 result;\n"); + if (uid_data->rgba6_format) + out.Write("\tresult.rgb = float3(prev.rgb >> 2) / 63.0;\n"); + else + out.Write("\tresult.rgb = float3(prev.rgb) / 255.0;\n"); + + // Colors will be blended against the 8-bit alpha from ocol1 and + // the 6-bit alpha from ocol0 will be written to the framebuffer + if (uid_data->useDstAlpha) + { + out.SetConstantsUsed(C_ALPHA, C_ALPHA); + out.Write("\tresult.a = float(" I_ALPHA ".a >> 2) / 63.0;\n"); + } + else + { + out.Write("\tresult.a = float(prev.a >> 2) / 63.0;\n"); + } + out.Write("\treturn result;\n"); + } + + out.Write("}}\n\n"); + + const bool use_dual_source = !uid_data->no_dual_src || uid_data->blend_enable; + if (!uid_data->uint_output && use_dual_source) + { + out.Write("vec4 dolphin_calculate_final_color1(ivec4 prev)\n"); + out.Write("{{\n"); + + // Colors will be blended against the 8-bit alpha from ocol1 and + // the 6-bit alpha from ocol0 will be written to the framebuffer + if (uid_data->useDstAlpha) + { + // Use dual-source color blending to perform dst alpha in a single pass + out.Write("\treturn vec4(0.0, 0.0, 0.0, float(prev.a) / 255.0);\n"); + } + else + { + out.Write("\treturn vec4(0.0, 0.0, 0.0, float(prev.a) / 255.0);\n"); + } + } + + out.Write("}}\n\n"); +} + +void WriteBlendHeader(ShaderCode& out, const pixel_shader_uid_data* uid_data) +{ + if (uid_data->blend_enable) + { + out.Write("vec4 dolphin_calculate_blend(vec4 initial_color, vec4 src_color)\n"); + out.Write("{{\n"); + using Common::EnumMap; + static constexpr EnumMap blend_src_factor{ + "float3(0,0,0);", // ZERO + "float3(1,1,1);", // ONE + "initial_color.rgb;", // DSTCLR + "float3(1,1,1) - initial_color.rgb;", // INVDSTCLR + "src_color.aaa;", // SRCALPHA + "float3(1,1,1) - src_color.aaa;", // INVSRCALPHA + "initial_color.aaa;", // DSTALPHA + "float3(1,1,1) - initial_color.aaa;", // INVDSTALPHA + }; + static constexpr EnumMap blend_src_factor_alpha{ + "0.0;", // ZERO + "1.0;", // ONE + "initial_color.a;", // DSTCLR + "1.0 - initial_color.a;", // INVDSTCLR + "src_color.a;", // SRCALPHA + "1.0 - src_color.a;", // INVSRCALPHA + "initial_color.a;", // DSTALPHA + "1.0 - initial_color.a;", // INVDSTALPHA + }; + static constexpr EnumMap blend_dst_factor{ + "float3(0,0,0);", // ZERO + "float3(1,1,1);", // ONE + "ocol0.rgb;", // SRCCLR + "float3(1,1,1) - ocol0.rgb;", // INVSRCCLR + "src_color.aaa;", // SRCALHA + "float3(1,1,1) - src_color.aaa;", // INVSRCALPHA + "initial_color.aaa;", // DSTALPHA + "float3(1,1,1) - initial_color.aaa;", // INVDSTALPHA + }; + static constexpr EnumMap blend_dst_factor_alpha{ + "0.0;", // ZERO + "1.0;", // ONE + "ocol0.a;", // SRCCLR + "1.0 - ocol0.a;", // INVSRCCLR + "src_color.a;", // SRCALPHA + "1.0 - src_color.a;", // INVSRCALPHA + "initial_ocol0.a;", // DSTALPHA + "1.0 - initial_color.a;", // INVDSTALPHA + }; + out.Write("\tfloat4 blend_src;"); + out.Write("\tblend_src.rgb = {}\n", blend_src_factor[uid_data->blend_src_factor]); + out.Write("\tblend_src.a = {}\n", blend_src_factor_alpha[uid_data->blend_src_factor_alpha]); + out.Write("\tfloat4 blend_dst;\n"); + out.Write("\tblend_dst.rgb = {}\n", blend_dst_factor[uid_data->blend_dst_factor]); + out.Write("\tblend_dst.a = {}\n", blend_dst_factor_alpha[uid_data->blend_dst_factor_alpha]); + + out.Write("\tfloat4 blend_result;\n"); + if (uid_data->blend_subtract) + { + out.Write("\tblend_result.rgb = initial_color.rgb * blend_dst.rgb - ocol0.rgb * " + "blend_src.rgb;\n"); + } + else + { + out.Write( + "\tblend_result.rgb = initial_color.rgb * blend_dst.rgb + ocol0.rgb * blend_src.rgb;\n"); + } + + if (uid_data->blend_subtract_alpha) + out.Write("\tblend_result.a = initial_color.a * blend_dst.a - ocol0.a * blend_src.a;\n"); + else + out.Write("\tblend_result.a = initial_color.a * blend_dst.a + ocol0.a * blend_src.a;\n"); + + out.Write("\treturn blend_result;\n"); + out.Write("}}\n\n"); + } +} + +void WriteEmulatedFragmentBodyHeader(APIType api_type, const ShaderHostConfig& host_config, + const pixel_shader_uid_data* uid_data, ShaderCode& out) +{ + constexpr std::string_view emulated_fragment_definition = + "void dolphin_emulated_fragment(in DolphinFragmentInput frag_input, out " + "DolphinFragmentOutput frag_output)"; + out.Write("{}\n", emulated_fragment_definition); + out.Write("{{\n"); + + WriteFragmentBody(api_type, host_config, uid_data, out); + + out.Write("}}\n"); +} + +void WriteFragmentDefinitions(APIType api_type, const ShaderHostConfig& host_config, + const pixel_shader_uid_data* uid_data, ShaderCode& out, + bool as_comment) +{ + out.Write("struct DolphinLightData\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"); + + out.Write("struct DolphinFragmentInput\n"); + out.Write("{{\n"); + out.Write("\tvec4 color_0;\n"); + out.Write("\tvec4 color_1;\n"); + out.Write("\tint layer;\n"); + out.Write("\tvec3 normal;\n"); + out.Write("\tvec3 position;\n"); + for (u32 i = 0; i < uid_data->genMode_numtexgens; i++) + { + out.Write("\tvec3 tex{};\n", i); + } + for (u32 i = uid_data->genMode_numtexgens; i < 8; i++) + { + out.Write("\tvec3 tex{};\n", i); + } + out.Write("\n"); + + out.Write("\tDolphinLightData[8] lights_chan0_color;\n"); + out.Write("\tDolphinLightData[8] lights_chan0_alpha;\n"); + out.Write("\tDolphinLightData[8] lights_chan1_color;\n"); + out.Write("\tDolphinLightData[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("}};\n\n"); + + out.Write("struct DolphinFragmentOutput\n"); + out.Write("{{\n"); + out.Write("\tivec4 main;\n"); + out.Write("\tivec4 last_texture;\n"); + out.Write("}};\n\n"); + + // CUSTOM_SHADER_LIGHTING_ATTENUATION_TYPE "enum" values + out.Write("const uint CUSTOM_SHADER_LIGHTING_ATTENUATION_TYPE_NONE = {}u;\n", + static_cast(AttenuationFunc::None)); + out.Write("const uint CUSTOM_SHADER_LIGHTING_ATTENUATION_TYPE_POINT = {}u;\n", + static_cast(AttenuationFunc::Spec)); + out.Write("const uint CUSTOM_SHADER_LIGHTING_ATTENUATION_TYPE_DIR = {}u;\n", + static_cast(AttenuationFunc::Dir)); + out.Write("const uint CUSTOM_SHADER_LIGHTING_ATTENUATION_TYPE_SPOT = {}u;\n", + static_cast(AttenuationFunc::Spot)); +} + +void WriteFragmentBody(APIType api_type, const ShaderHostConfig& host_config, + const pixel_shader_uid_data* uid_data, ShaderCode& out) +{ + const bool per_pixel_lighting = host_config.per_pixel_lighting; + const bool stereo = host_config.stereo; + const u32 numStages = uid_data->genMode_numtevstages + 1; + + out.Write("\tvec4 col0 = frag_input.color_0;\n"); + out.Write("\tvec4 col1 = frag_input.color_1;\n"); + out.Write("\tint layer = frag_input.layer;\n"); + + out.Write("\tint4 c0 = " I_COLORS "[1], c1 = " I_COLORS "[2], c2 = " I_COLORS + "[3], prev = " I_COLORS "[0];\n" + "\tint4 rastemp = int4(0, 0, 0, 0), textemp = int4(0, 0, 0, 0), konsttemp = int4(0, 0, " + "0, 0);\n" + "\tint3 comp16 = int3(1, 256, 0), comp24 = int3(1, 256, 256*256);\n" + "\tint alphabump=0;\n" + "\tint3 tevcoord=int3(0, 0, 0);\n" + "\tint2 wrappedcoord=int2(0,0), tempcoord=int2(0,0);\n" + "\tint4 " + "tevin_a=int4(0,0,0,0),tevin_b=int4(0,0,0,0),tevin_c=int4(0,0,0,0),tevin_d=int4(0,0,0," + "0);\n\n"); // tev combiner inputs + + if (per_pixel_lighting) + { + if (uid_data->numColorChans > 0) + { + out.Write("\tcol0 = dolphin_calculate_lighting_chn0(col0, vec4(frag_input.position, 1), " + "frag_input.normal);\n"); + } + else + { + // The number of colors available to TEV is determined by numColorChans. + // We have to provide the fields to match the interface, so set to zero if it's not enabled. + out.Write("\tcol0 = vec4(0.0, 0.0, 0.0, 0.0);\n"); + } + + if (uid_data->numColorChans == 2) + { + out.Write("\tcol1 = dolphin_calculate_lighting_chn1(col1, vec4(frag_input.position, 1), " + "frag_input.normal);\n"); + } + else + { + // The number of colors available to TEV is determined by numColorChans. + // We have to provide the fields to match the interface, so set to zero if it's not enabled. + out.Write("\tcol1 = vec4(0.0, 0.0, 0.0, 0.0);\n"); + } + } + + if (uid_data->genMode_numtexgens == 0) + { + // TODO: This is a hack to ensure that shaders still compile when setting out of bounds tex + // coord indices to 0. Ideally, it shouldn't exist at all, but the exact behavior hasn't been + // tested. + out.Write("\tint2 fixpoint_uv0 = int2(0, 0);\n\n"); + } + else + { + out.SetConstantsUsed(C_TEXDIMS, C_TEXDIMS + uid_data->genMode_numtexgens - 1); + for (u32 i = 0; i < uid_data->genMode_numtexgens; ++i) + { + out.Write("\tint2 fixpoint_uv{} = int2(", i); + out.Write("(frag_input.tex{}.z == 0.0 ? frag_input.tex{}.xy : frag_input.tex{}.xy / " + "frag_input.tex{}.z)", + i, i, i, i); + out.Write(" * float2(" I_TEXDIMS "[{}].zw * 128));\n", i); + // TODO: S24 overflows here? + } + } + + 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.SetConstantsUsed(C_INDTEXSCALE + i / 2, C_INDTEXSCALE + i / 2); + out.Write("\ttempcoord = fixpoint_uv{} >> " I_INDTEXSCALE "[{}].{};\n", texcoord, i / 2, + (i & 1) ? "zw" : "xy"); + + out.Write("\tint3 iindtex{0} = sampleTextureWrapper({1}u, tempcoord, layer).abg;\n", i, + texmap); + } + } + + for (u32 i = 0; i < numStages; i++) + { + // Build the equation for this stage + WriteStage(out, uid_data, i, api_type, stereo, false); + } + + { + // The results of the last texenv stage are put onto the screen, + // regardless of the used destination register + TevStageCombiner::ColorCombiner last_cc; + TevStageCombiner::AlphaCombiner last_ac; + last_cc.hex = uid_data->stagehash[uid_data->genMode_numtevstages].cc; + last_ac.hex = uid_data->stagehash[uid_data->genMode_numtevstages].ac; + if (last_cc.dest != TevOutput::Prev) + { + out.Write("\tprev.rgb = {};\n", tev_c_output_table[last_cc.dest]); + } + if (last_ac.dest != TevOutput::Prev) + { + out.Write("\tprev.a = {};\n", tev_a_output_table[last_ac.dest]); + } + } + + out.Write("\tfrag_output.last_texture = textemp;\n"); + out.Write("\tfrag_output.main = prev;\n"); +} + +ShaderCode WriteFullShader(APIType api_type, const ShaderHostConfig& host_config, + const pixel_shader_uid_data* uid_data, std::string_view custom_pixel, + std::string_view custom_uniforms) +{ + ShaderCode out; + + const bool per_pixel_lighting = g_ActiveConfig.bEnablePixelLighting; + const bool msaa = host_config.msaa; + const bool ssaa = host_config.ssaa; + const bool stereo = host_config.stereo; + const u32 numStages = uid_data->genMode_numtevstages + 1; + + out.Write("// Pixel Shader for TEV stages\n"); + out.Write("// {} TEV stages, {} texgens, {} IND stages\n", numStages, + uid_data->genMode_numtexgens, uid_data->genMode_numindstages); + + // Stuff that is shared between ubershaders and pixelgen. + WriteBitfieldExtractHeader(out, api_type, host_config); + + WritePixelShaderCommonHeader(out, api_type, host_config, uid_data->bounding_box, {}); + + GenerateLightingShaderHeader(out, uid_data->lighting); + + WriteDitherHeader(api_type, host_config, uid_data, out); + + WriteFogHeader(api_type, host_config, uid_data, out); + + WriteDepthHeader(api_type, host_config, uid_data, out); + + WriteLogicOpHeader(api_type, host_config, uid_data, out); + + WriteColorHeader(api_type, host_config, uid_data, out); + + WriteBlendHeader(out, uid_data); + + out.Write("\n#define sampleTextureWrapper(texmap, uv, layer) " + "sampleTexture(texmap, samp[texmap], uv, layer)\n"); + + if (uid_data->ztest == EmulatedZ::ForcedEarly) + { + // Zcomploc (aka early_ztest) is a way to control whether depth test is done before + // or after texturing and alpha test. PC graphics APIs used to provide no way to emulate + // this feature properly until 2012: Depth tests were always done after alpha testing. + // Most importantly, it was not possible to write to the depth buffer without also writing + // a color value (unless color writing was disabled altogether). + + // OpenGL 4.2 actually provides two extensions which can force an early z test: + // * ARB_image_load_store has 'layout(early_fragment_tests)' which forces the driver to do z + // and stencil tests early. + // * ARB_conservative_depth has 'layout(depth_unchanged) which signals to the driver that it + // can make optimisations + // which assume the pixel shader won't update the depth buffer. + + // early_fragment_tests is the best option, as it requires the driver to do early-z and defines + // early-z exactly as + // we expect, with discard causing the shader to exit with only the depth buffer updated. + + // Conservative depth's 'depth_unchanged' only hints to the driver that an early-z optimisation + // can be made and + // doesn't define what will happen if we discard the fragment. But the way modern graphics + // hardware is implemented + // means it is not unreasonable to expect the same behaviour as early_fragment_tests. + // We can also assume that if a driver has gone out of its way to support conservative depth and + // not image_load_store + // as required by OpenGL 4.2 that it will be doing the optimisation. + // If the driver doesn't actually do an early z optimisation, ZCompLoc will be broken and depth + // will only be written + // if the alpha test passes. + + // We support Conservative as a fallback, because many drivers based on Mesa haven't implemented + // all of the + // ARB_image_load_store extension yet. + + // This is a #define which signals whatever early-z method the driver supports. + out.Write("FORCE_EARLY_Z; \n"); + } + + const bool use_framebuffer_fetch = uid_data->blend_enable || uid_data->logic_op_enable || + uid_data->ztest == EmulatedZ::EarlyWithFBFetch; + +#ifdef __APPLE__ + // Framebuffer fetch is only supported by Metal, so ensure that we're running Vulkan (MoltenVK) + // if we want to use it. + if (api_type == APIType::Vulkan || api_type == APIType::Metal) + { + if (!uid_data->no_dual_src) + { + out.Write("FRAGMENT_OUTPUT_LOCATION_INDEXED(0, 0) out vec4 {};\n" + "FRAGMENT_OUTPUT_LOCATION_INDEXED(0, 1) out vec4 ocol1;\n", + use_framebuffer_fetch ? "real_ocol0" : "ocol0"); + } + else + { + // Metal doesn't support a single unified variable for both input and output, + // so when using framebuffer fetch, we declare the input separately below. + out.Write("FRAGMENT_OUTPUT_LOCATION(0) out vec4 {};\n", + use_framebuffer_fetch ? "real_ocol0" : "ocol0"); + } + + if (use_framebuffer_fetch) + { + // Subpass inputs will be converted to framebuffer fetch by SPIRV-Cross. + out.Write("INPUT_ATTACHMENT_BINDING(0, 0, 0) uniform subpassInput in_ocol0;\n"); + } + } + else +#endif + { + if (use_framebuffer_fetch) + { + out.Write("FRAGMENT_OUTPUT_LOCATION(0) FRAGMENT_INOUT vec4 real_ocol0;\n"); + } + else + { + out.Write("FRAGMENT_OUTPUT_LOCATION_INDEXED(0, 0) out {} ocol0;\n", + uid_data->uint_output ? "uvec4" : "vec4"); + } + + if (!uid_data->no_dual_src) + { + out.Write("{} out {} ocol1;\n", "FRAGMENT_OUTPUT_LOCATION_INDEXED(0, 1)", + uid_data->uint_output ? "uvec4" : "vec4"); + } + } + + if (uid_data->per_pixel_depth) + out.Write("#define depth gl_FragDepth\n"); + + if (host_config.backend_geometry_shaders) + { + out.Write("VARYING_LOCATION(0) in VertexData {{\n"); + GenerateVSOutputMembers(out, api_type, uid_data->genMode_numtexgens, host_config, + GetInterpolationQualifier(msaa, ssaa, true, true), ShaderStage::Pixel); + + out.Write("}};\n"); + if (stereo && !host_config.backend_gl_layer_in_fs) + out.Write("flat in int layer;"); + } + else + { + // Let's set up attributes + u32 counter = 0; + out.Write("VARYING_LOCATION({}) {} in float4 colors_0;\n", counter++, + GetInterpolationQualifier(msaa, ssaa)); + out.Write("VARYING_LOCATION({}) {} in float4 colors_1;\n", counter++, + GetInterpolationQualifier(msaa, ssaa)); + for (u32 i = 0; i < uid_data->genMode_numtexgens; ++i) + { + out.Write("VARYING_LOCATION({}) {} in float3 tex{};\n", counter++, + GetInterpolationQualifier(msaa, ssaa), i); + } + if (!host_config.fast_depth_calc) + { + out.Write("VARYING_LOCATION({}) {} in float4 clipPos;\n", counter++, + GetInterpolationQualifier(msaa, ssaa)); + } + if (per_pixel_lighting) + { + out.Write("VARYING_LOCATION({}) {} in float3 Normal;\n", counter++, + GetInterpolationQualifier(msaa, ssaa)); + out.Write("VARYING_LOCATION({}) {} in float3 WorldPos;\n", counter++, + GetInterpolationQualifier(msaa, ssaa)); + } + } + + WriteFragmentDefinitions(api_type, host_config, uid_data, out, false); + + if (!custom_uniforms.empty()) + { + out.Write("UBO_BINDING(std140, 3) uniform CustomShaderBlock {{\n"); + out.Write("{}", custom_uniforms); + out.Write("}} custom_uniforms;\n"); + } + + WriteEmulatedFragmentBodyHeader(api_type, host_config, uid_data, out); + + if (custom_pixel.empty()) + { + out.Write("{}\n", fragment_definition); + out.Write("{{\n"); + + out.Write("\tdolphin_emulated_fragment(frag_input, frag_output);\n"); + + out.Write("}}\n"); + } + else + { + out.Write("{}\n", custom_pixel); + } + + out.Write("void main()\n{{\n"); + out.Write("\tfloat4 rawpos = gl_FragCoord;\n"); + + if (use_framebuffer_fetch) + { + // Store off a copy of the initial framebuffer value. + // + // If FB_FETCH_VALUE isn't defined (i.e. no special keyword for fetching from the + // framebuffer), we read from real_ocol0. + out.Write("#ifdef FB_FETCH_VALUE\n" + "\tfloat4 initial_ocol0 = FB_FETCH_VALUE;\n" + "#else\n" + "\tfloat4 initial_ocol0 = real_ocol0;\n" + "#endif\n"); + + // QComm's Adreno driver doesn't seem to like using the framebuffer_fetch value as an + // intermediate value with multiple reads & modifications, so we pull out the "real" output + // value above and use a temporary for calculations, then set the output value once at the + // end of the shader. + out.Write("\tfloat4 ocol0;\n"); + } + + if (uid_data->blend_enable) + { + out.Write("\tfloat4 ocol1;\n"); + } + + if (host_config.backend_geometry_shaders && stereo) + { + if (host_config.backend_gl_layer_in_fs) + out.Write("\tint layer = gl_Layer;\n"); + } + else + { + out.Write("\tint layer = 0;\n"); + } + + out.Write("\tDolphinFragmentInput frag_input;\n"); + out.Write("\tfrag_input.color_0 = colors_0;\n"); + out.Write("\tfrag_input.color_1 = colors_1;\n"); + out.Write("\tfrag_input.layer = layer;\n"); + if (per_pixel_lighting) + { + out.Write("\tfrag_input.normal = normalize(Normal);\n"); + out.Write("\tfrag_input.position = WorldPos;\n"); + } + else + { + out.Write("\tfrag_input.normal = vec3(0, 0, 0);\n"); + out.Write("\tfrag_input.position = vec3(0, 0, 0);\n"); + } + for (u32 i = 0; i < uid_data->genMode_numtexgens; i++) + { + out.Write("\tfrag_input.tex{0} = tex{0};\n", i); + } + + if (!custom_pixel.empty()) + GenerateCustomLighting(&out, uid_data->lighting); + + out.Write("\tDolphinFragmentOutput frag_output;\n"); + out.Write("\tfragment(frag_input, frag_output);\n"); + out.Write("\tivec4 prev = frag_output.main & 255;\n"); + + // NOTE: Fragment may not be discarded if alpha test always fails and early depth test is enabled + // (in this case we need to write a depth value if depth test passes regardless of the alpha + // testing result) + if (uid_data->Pretest == AlphaTestResult::Undetermined || + (uid_data->Pretest == AlphaTestResult::Fail && uid_data->ztest == EmulatedZ::Late)) + { + WriteAlphaTest(out, uid_data, api_type, uid_data->per_pixel_depth, + !uid_data->no_dual_src || uid_data->blend_enable); + } + + // This situation is important for Mario Kart Wii's menus (they will render incorrectly if the + // alpha test for the FMV in the background fails, since they depend on depth for drawing a yellow + // border) and Fortune Street's gameplay (where a rectangle with an alpha value of 1 is drawn over + // the center of the screen several times, but those rectangles shouldn't be visible). + // Blending seems to result in no changes to the output with an alpha of 1, even if the input + // color is white. + // TODO: Investigate this further: we might be handling blending incorrectly in general (though + // there might not be any good way of changing blending behavior) + out.Write("\t// Hardware testing indicates that an alpha of 1 can pass an alpha test,\n" + "\t// but doesn't do anything in blending\n" + "\tif (prev.a == 1) prev.a = 0;\n"); + + const bool write_depth = + uid_data->ztest == EmulatedZ::Early || uid_data->ztest == EmulatedZ::EarlyWithFBFetch || + uid_data->ztest == EmulatedZ::EarlyWithZComplocHack || uid_data->ztest == EmulatedZ::Late; + const bool needs_depth = uid_data->per_pixel_depth && write_depth; + const bool needs_zcoord = needs_depth || uid_data->fog_fsel != FogType::Off; + if (needs_zcoord) + { + if (!host_config.fast_depth_calc) + { + out.Write( + "\tint zCoord = dolphin_calculate_zcoord(rawpos, clipPos, frag_output.last_texture);\n"); + } + else + { + out.Write("\tint zCoord = dolphin_calculate_zcoord(rawpos, vec4(0, 0, 0, 0), " + "frag_output.last_texture);\n"); + } + } + + if (needs_depth) + { + if (!host_config.backend_reversed_depth_range) + out.Write("\tdepth = 1.0 - float(zCoord) / 16777216.0;\n"); + else + out.Write("\tdepth = float(zCoord) / 16777216.0;\n"); + } + + // No dithering for RGB8 mode + if (uid_data->dither) + out.Write("\tprev.rgb = dolphin_calculate_dither(rawpos, prev);\n"); + + if (uid_data->fog_fsel != FogType::Off) + out.Write("\tprev.rgb = dolphin_calculate_fog(prev, rawpos, zCoord);\n"); + + if (uid_data->logic_op_enable) + out.Write("\tprev = dolphin_calculate_logicop(initial_ocol0, prev);\n"); + else if (uid_data->emulate_logic_op_with_blend) + WriteLogicOpBlend(out, uid_data); + + // Write the color and alpha values to the framebuffer + // If using shader blend, we still use the separate alpha + out.Write("\tocol0 = dolphin_calculate_final_color0(prev);\n"); + + const bool use_dual_source = !uid_data->no_dual_src || uid_data->blend_enable; + if (use_dual_source) + out.Write("\tocol1 = dolphin_calculate_final_color1(prev);\n"); + + if (uid_data->blend_enable) + { + if (uid_data->useDstAlpha) + out.Write("\tocol0 = dolphin_calculate_blend(initial_ocol0, ocol1);\n"); + else + out.Write("\tocol0 = dolphin_calculate_blend(initial_ocol0, ocol0);\n"); + } + + if (use_framebuffer_fetch) + out.Write("\treal_ocol0 = ocol0;\n"); + + if (uid_data->bounding_box) + out.Write("\tUpdateBoundingBox(rawpos.xy);\n"); + + out.Write("}}\n"); + + return out; +} +} // namespace PixelShader diff --git a/Source/Core/VideoCommon/PixelShaderGen.h b/Source/Core/VideoCommon/PixelShaderGen.h index e5dd43d754..8a013458ac 100644 --- a/Source/Core/VideoCommon/PixelShaderGen.h +++ b/Source/Core/VideoCommon/PixelShaderGen.h @@ -170,3 +170,18 @@ void WritePixelShaderCommonHeader(ShaderCode& out, APIType api_type, void ClearUnusedPixelShaderUidBits(APIType api_type, const ShaderHostConfig& host_config, PixelShaderUid* uid); PixelShaderUid GetPixelShaderUid(); + +namespace PixelShader +{ +constexpr std::string_view fragment_definition = + "void fragment(in DolphinFragmentInput frag_input, out DolphinFragmentOutput frag_output)"; + +void WriteFragmentDefinitions(APIType api_type, const ShaderHostConfig& host_config, + const pixel_shader_uid_data* uid_data, ShaderCode& out, + bool as_comment); +void WriteFragmentBody(APIType api_type, const ShaderHostConfig& host_config, + const pixel_shader_uid_data* uid_data, ShaderCode& out); +ShaderCode WriteFullShader(APIType api_type, const ShaderHostConfig& host_config, + const pixel_shader_uid_data* uid_data, std::string_view custom_pixel, + std::string_view custom_uniforms); +} // namespace PixelShader diff --git a/Source/Core/VideoCommon/PixelShaderManager.h b/Source/Core/VideoCommon/PixelShaderManager.h index 5d3fe7257e..fc7f3609c7 100644 --- a/Source/Core/VideoCommon/PixelShaderManager.h +++ b/Source/Core/VideoCommon/PixelShaderManager.h @@ -55,7 +55,7 @@ public: bool dirty = false; // Constants for custom shaders - std::span custom_constants; + std::span custom_constants; bool custom_constants_dirty = false; private: diff --git a/Source/Core/VideoCommon/ShaderCache.cpp b/Source/Core/VideoCommon/ShaderCache.cpp index 22939e9b90..43402fe691 100644 --- a/Source/Core/VideoCommon/ShaderCache.cpp +++ b/Source/Core/VideoCommon/ShaderCache.cpp @@ -37,10 +37,11 @@ ShaderCache::~ShaderCache() ClearCaches(); } -bool ShaderCache::Initialize() +bool ShaderCache::Initialize(bool force_no_cache) { m_api_type = g_backend_info.api_type; m_host_config.bits = ShaderHostConfig::GetCurrent().bits; + m_should_cache = g_ActiveConfig.bShaderCache && !force_no_cache; if (!CompileSharedPipelines()) return false; @@ -56,7 +57,7 @@ void ShaderCache::InitializeShaderCache() m_async_shader_compiler->ResizeWorkerThreads(g_ActiveConfig.GetShaderPrecompilerThreads()); // Load shader and UID caches. - if (g_ActiveConfig.bShaderCache && m_api_type != APIType::Nothing) + if (m_should_cache && m_api_type != APIType::Nothing) { LoadCaches(); LoadPipelineUIDCache(); @@ -84,7 +85,7 @@ void ShaderCache::Reload() if (!CompileSharedPipelines()) PanicAlertFmt("Failed to compile shared pipelines after reload."); - if (g_ActiveConfig.bShaderCache) + if (m_should_cache) LoadCaches(); // Switch to the precompiling shader configuration while we rebuild. @@ -125,7 +126,7 @@ const AbstractPipeline* ShaderCache::GetPipelineForUid(const GXPipelineUid& uid) std::optional pipeline_config = GetGXPipelineConfig(uid); if (pipeline_config) pipeline = g_gfx->CreatePipeline(*pipeline_config); - if (g_ActiveConfig.bShaderCache && !exists_in_cache) + if (m_should_cache && !exists_in_cache) AppendGXPipelineUID(uid); return InsertGXPipeline(uid, std::move(pipeline)); } @@ -470,7 +471,7 @@ const AbstractShader* ShaderCache::InsertVertexShader(const VertexShaderUid& uid if (shader && !entry.shader) { - if (g_ActiveConfig.bShaderCache && g_backend_info.bSupportsShaderBinaries) + if (m_should_cache && g_backend_info.bSupportsShaderBinaries) { auto binary = shader->GetBinary(); if (!binary.empty()) @@ -492,7 +493,7 @@ const AbstractShader* ShaderCache::InsertVertexUberShader(const UberShader::Vert if (shader && !entry.shader) { - if (g_ActiveConfig.bShaderCache && g_backend_info.bSupportsShaderBinaries) + if (m_should_cache && g_backend_info.bSupportsShaderBinaries) { auto binary = shader->GetBinary(); if (!binary.empty()) @@ -514,7 +515,7 @@ const AbstractShader* ShaderCache::InsertPixelShader(const PixelShaderUid& uid, if (shader && !entry.shader) { - if (g_ActiveConfig.bShaderCache && g_backend_info.bSupportsShaderBinaries) + if (m_should_cache && g_backend_info.bSupportsShaderBinaries) { auto binary = shader->GetBinary(); if (!binary.empty()) @@ -536,7 +537,7 @@ const AbstractShader* ShaderCache::InsertPixelUberShader(const UberShader::Pixel if (shader && !entry.shader) { - if (g_ActiveConfig.bShaderCache && g_backend_info.bSupportsShaderBinaries) + if (m_should_cache && g_backend_info.bSupportsShaderBinaries) { auto binary = shader->GetBinary(); if (!binary.empty()) @@ -563,7 +564,7 @@ const AbstractShader* ShaderCache::CreateGeometryShader(const GeometryShaderUid& if (shader && !entry.shader) { - if (g_ActiveConfig.bShaderCache && g_backend_info.bSupportsShaderBinaries) + if (m_should_cache && g_backend_info.bSupportsShaderBinaries) { auto binary = shader->GetBinary(); if (!binary.empty()) @@ -875,7 +876,7 @@ const AbstractPipeline* ShaderCache::InsertGXPipeline(const GXPipelineUid& confi { entry.first = std::move(pipeline); - if (g_ActiveConfig.bShaderCache) + if (m_should_cache) { auto cache_data = entry.first->GetCacheData(); if (!cache_data.empty()) @@ -901,7 +902,7 @@ ShaderCache::InsertGXUberPipeline(const GXUberPipelineUid& config, { entry.first = std::move(pipeline); - if (g_ActiveConfig.bShaderCache) + if (m_should_cache) { auto cache_data = entry.first->GetCacheData(); if (!cache_data.empty()) diff --git a/Source/Core/VideoCommon/ShaderCache.h b/Source/Core/VideoCommon/ShaderCache.h index c76f7dac16..7d84d99136 100644 --- a/Source/Core/VideoCommon/ShaderCache.h +++ b/Source/Core/VideoCommon/ShaderCache.h @@ -47,7 +47,7 @@ public: ~ShaderCache(); // Perform at startup, create descriptor layouts, compiles all static shaders. - bool Initialize(); + bool Initialize(bool force_no_cache = false); void Shutdown(); // Compiles/loads cached shaders. @@ -252,6 +252,8 @@ private: // Texture decoding shaders std::map, std::unique_ptr> m_texture_decoding_shaders; + bool m_should_cache = false; + Common::EventHook m_frame_end_handler; }; diff --git a/Source/Core/VideoCommon/ShaderGenCommon.h b/Source/Core/VideoCommon/ShaderGenCommon.h index 4723cbfc79..5b19720df9 100644 --- a/Source/Core/VideoCommon/ShaderGenCommon.h +++ b/Source/Core/VideoCommon/ShaderGenCommon.h @@ -319,6 +319,7 @@ static const char s_shader_uniforms[] = "\tuint components;\n" "\tuint vertex_offset_rawcolor0;\n" "\tuint vertex_offset_rawcolor1;\n" "\tuint4 vertex_offset_rawtex[2];\n" // std140 is pain + "\tfloat4 custom_transform[4];\n" "\t#define xfmem_texMtxInfo(i) (xfmem_pack1[(i)].x)\n" "\t#define xfmem_postMtxInfo(i) (xfmem_pack1[(i)].y)\n" "\t#define xfmem_color(i) (xfmem_pack1[(i)].z)\n" diff --git a/Source/Core/VideoCommon/TextureCacheBase.cpp b/Source/Core/VideoCommon/TextureCacheBase.cpp index bbecc6437c..36c87497a4 100644 --- a/Source/Core/VideoCommon/TextureCacheBase.cpp +++ b/Source/Core/VideoCommon/TextureCacheBase.cpp @@ -4,6 +4,7 @@ #include "VideoCommon/TextureCacheBase.h" #include +#include #include #include #include @@ -39,10 +40,9 @@ #include "VideoCommon/Assets/CustomTextureData.h" #include "VideoCommon/BPMemory.h" #include "VideoCommon/FramebufferManager.h" -#include "VideoCommon/GraphicsModSystem/Runtime/FBInfo.h" -#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionData.h" +#include "VideoCommon/GraphicsModSystem/Runtime/CustomResourceManager.h" +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModBackend.h" #include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h" -#include "VideoCommon/HiresTextures.h" #include "VideoCommon/OpcodeDecoding.h" #include "VideoCommon/PixelShaderManager.h" #include "VideoCommon/Present.h" @@ -135,6 +135,31 @@ void TextureCacheBase::Invalidate() FlushEFBCopies(); TMEM::InvalidateAll(); + if (g_ActiveConfig.bGraphicMods) + { + auto& system = Core::System::GetInstance(); + auto& mod_manager = system.GetGraphicsModManager(); + for (auto& tex : m_textures_by_address) + { + const auto& entry = tex.second; + if (entry->is_efb_copy) + { + mod_manager.GetBackend().OnTextureUnload(GraphicsModSystem::TextureType::EFB, + entry->texture_info_name); + } + else if (entry->is_xfb_copy) + { + mod_manager.GetBackend().OnTextureUnload(GraphicsModSystem::TextureType::XFB, + entry->texture_info_name); + } + else + { + mod_manager.GetBackend().OnTextureUnload(GraphicsModSystem::TextureType::Normal, + entry->texture_info_name); + } + } + } + for (auto& bind : m_bound_textures) bind.reset(); m_textures_by_hash.clear(); @@ -261,25 +286,10 @@ void TextureCacheBase::SetBackupConfig(const VideoConfig& config) bool TextureCacheBase::DidLinkedAssetsChange(const TCacheEntry& entry) { - for (const auto& cached_asset : entry.linked_game_texture_assets) - { - if (cached_asset.m_asset) - { - if (cached_asset.m_asset->GetLastLoadedTime() > cached_asset.m_cached_write_time) - return true; - } - } + if (!entry.hires_texture) + return false; - for (const auto& cached_asset : entry.linked_asset_dependencies) - { - if (cached_asset.m_asset) - { - if (cached_asset.m_asset->GetLastLoadedTime() > cached_asset.m_cached_write_time) - return true; - } - } - - return false; + return entry.last_custom_texture_data != entry.hires_texture->LoadTexture(); } RcTcacheEntry TextureCacheBase::ApplyPaletteToEntry(RcTcacheEntry& entry, const u8* palette, @@ -1292,16 +1302,21 @@ TCacheEntry* TextureCacheBase::LoadImpl(const TextureInfo& texture_info, bool fo return nullptr; entry->frameCount = FRAMECOUNT_INVALID; - if (entry->texture_info_name.empty() && g_ActiveConfig.bGraphicMods) + if (g_ActiveConfig.bGraphicMods) { - entry->texture_info_name = texture_info.CalculateTextureName().GetFullName(); - - GraphicsModActionData::TextureLoad texture_load{entry->texture_info_name}; - for (const auto& action : - g_graphics_mod_manager->GetTextureLoadActions(entry->texture_info_name)) + if (entry->texture_info_name.empty()) { - action->OnTextureLoad(&texture_load); + entry->texture_info_name = texture_info.CalculateTextureName().GetFullName(); } + + GraphicsModSystem::TextureView texture; + texture.hash_name = entry->texture_info_name; + texture.texture_data = entry->texture.get(); + texture.texture_type = GraphicsModSystem::TextureType::Normal; + texture.unit = texture_info.GetStage(); + auto& system = Core::System::GetInstance(); + auto& mod_manager = system.GetGraphicsModManager(); + mod_manager.GetBackend().OnTextureLoad(texture); } m_bound_textures[texture_info.GetStage()] = entry; @@ -1568,81 +1583,41 @@ RcTcacheEntry TextureCacheBase::GetTexture(const int textureCacheSafetyColorSamp InvalidateTexture(oldest_entry); } - std::vector> cached_game_assets; - std::vector> data_for_assets; + std::shared_ptr hires_texture; bool has_arbitrary_mipmaps = false; bool skip_texture_dump = false; - std::shared_ptr hires_texture; + VideoCommon::CustomTextureData* custom_texture_data = nullptr; if (g_ActiveConfig.bHiresTextures) { hires_texture = HiresTexture::Search(texture_info); if (hires_texture) { - auto asset = hires_texture->GetAsset(); - const auto loaded_time = asset->GetLastLoadedTime(); - cached_game_assets.push_back( - VideoCommon::CachedAsset{std::move(asset), loaded_time}); has_arbitrary_mipmaps = hires_texture->HasArbitraryMipmaps(); + custom_texture_data = hires_texture->LoadTexture(); skip_texture_dump = true; } } - std::vector> additional_dependencies; - - std::string texture_name = ""; - - if (g_ActiveConfig.bGraphicMods) - { - u32 height = texture_info.GetRawHeight(); - u32 width = texture_info.GetRawWidth(); - if (hires_texture) - { - auto asset = hires_texture->GetAsset(); - if (asset) - { - auto data = asset->GetData(); - if (data) - { - if (!data->m_texture.m_slices.empty()) - { - if (!data->m_texture.m_slices[0].m_levels.empty()) - { - height = data->m_texture.m_slices[0].m_levels[0].height; - width = data->m_texture.m_slices[0].m_levels[0].width; - } - } - } - } - } - texture_name = texture_info.CalculateTextureName().GetFullName(); - GraphicsModActionData::TextureCreate texture_create{ - texture_name, width, height, &cached_game_assets, &additional_dependencies}; - for (const auto& action : g_graphics_mod_manager->GetTextureCreateActions(texture_name)) - { - action->OnTextureCreate(&texture_create); - } - } - - data_for_assets.reserve(cached_game_assets.size()); - for (auto& cached_asset : cached_game_assets) - { - auto data = cached_asset.m_asset->GetData(); - if (data) - { - if (cached_asset.m_asset->Validate(texture_info.GetRawWidth(), texture_info.GetRawHeight())) - { - data_for_assets.push_back(data); - } - } - } - auto entry = CreateTextureEntry(TextureCreationInfo{base_hash, full_hash, bytes_per_block, palette_size}, - texture_info, textureCacheSafetyColorSampleSize, - std::move(data_for_assets), has_arbitrary_mipmaps, skip_texture_dump); - entry->linked_game_texture_assets = std::move(cached_game_assets); - entry->linked_asset_dependencies = std::move(additional_dependencies); - entry->texture_info_name = std::move(texture_name); + texture_info, textureCacheSafetyColorSampleSize, custom_texture_data, + has_arbitrary_mipmaps, skip_texture_dump); + entry->hires_texture = std::move(hires_texture); + entry->last_custom_texture_data = custom_texture_data; + + if (g_ActiveConfig.bGraphicMods) + { + entry->texture_info_name = texture_info.CalculateTextureName().GetFullName(); + + GraphicsModSystem::TextureView texture; + texture.hash_name = entry->texture_info_name; + texture.texture_data = entry->texture.get(); + texture.texture_type = GraphicsModSystem::TextureType::Normal; + texture.unit = texture_info.GetStage(); + auto& system = Core::System::GetInstance(); + auto& mod_manager = system.GetGraphicsModManager(); + mod_manager.GetBackend().OnTextureCreate(texture); + } return entry; } @@ -1651,8 +1626,7 @@ RcTcacheEntry TextureCacheBase::GetTexture(const int textureCacheSafetyColorSamp // expected because each texture is loaded into a texture array RcTcacheEntry TextureCacheBase::CreateTextureEntry( const TextureCreationInfo& creation_info, const TextureInfo& texture_info, - const int safety_color_sample_size, - std::vector> assets_data, + const int safety_color_sample_size, VideoCommon::CustomTextureData* custom_texture_data, const bool custom_arbitrary_mipmaps, bool skip_texture_dump) { #ifdef __APPLE__ @@ -1662,36 +1636,22 @@ RcTcacheEntry TextureCacheBase::CreateTextureEntry( #endif RcTcacheEntry entry; - if (!assets_data.empty()) + if (custom_texture_data) { - const auto calculate_max_levels = [&]() { - const auto max_element = std::max_element( - assets_data.begin(), assets_data.end(), [](const auto& lhs, const auto& rhs) { - return lhs->m_texture.m_slices[0].m_levels.size() < - rhs->m_texture.m_slices[0].m_levels.size(); - }); - return (*max_element)->m_texture.m_slices[0].m_levels.size(); - }; - const u32 texLevels = no_mips ? 1 : (u32)calculate_max_levels(); - const auto& first_level = assets_data[0]->m_texture.m_slices[0].m_levels[0]; - const TextureConfig config(first_level.width, first_level.height, texLevels, - static_cast(assets_data.size()), 1, first_level.format, 0, - AbstractTextureType::Texture_2DArray); + const u32 texLevels = no_mips ? 1 : (u32)custom_texture_data->m_slices[0].m_levels.size(); + const auto& first_level = custom_texture_data->m_slices[0].m_levels[0]; + const TextureConfig config(first_level.width, first_level.height, texLevels, 1, 1, + first_level.format, 0, AbstractTextureType::Texture_2DArray); entry = AllocateCacheEntry(config); if (!entry) [[unlikely]] return entry; - for (u32 data_index = 0; data_index < static_cast(assets_data.size()); data_index++) + const auto& slice = custom_texture_data->m_slices[0]; + for (u32 level_index = 0; + level_index < std::min(texLevels, static_cast(slice.m_levels.size())); ++level_index) { - const auto& asset = assets_data[data_index]; - const auto& slice = asset->m_texture.m_slices[0]; - for (u32 level_index = 0; - level_index < std::min(texLevels, static_cast(slice.m_levels.size())); - ++level_index) - { - const auto& level = slice.m_levels[level_index]; - entry->texture->Load(level_index, level.width, level.height, level.row_length, - level.data.data(), level.data.size(), data_index); - } + const auto& level = slice.m_levels[level_index]; + entry->texture->Load(level_index, level.width, level.height, level.row_length, + level.data.data(), level.data.size()); } entry->has_arbitrary_mips = custom_arbitrary_mipmaps; @@ -2296,36 +2256,6 @@ void TextureCacheBase::CopyRenderTargetToTexture( return; } - if (g_ActiveConfig.bGraphicMods) - { - FBInfo info; - info.m_width = tex_w; - info.m_height = tex_h; - info.m_texture_format = baseFormat; - if (is_xfb_copy) - { - for (const auto& action : g_graphics_mod_manager->GetXFBActions(info)) - { - action->OnXFB(); - } - } - else - { - bool skip = false; - GraphicsModActionData::EFB efb{tex_w, tex_h, &skip, &scaled_tex_w, &scaled_tex_h}; - for (const auto& action : g_graphics_mod_manager->GetEFBActions(info)) - { - action->OnEFB(&efb); - } - if (skip == true) - { - if (copy_to_ram) - UninitializeEFBMemory(dst, dstStride, bytes_per_row, num_blocks_y); - return; - } - } - } - if (dstStride < bytes_per_row) { // This kind of efb copy results in a scrambled image. @@ -2379,7 +2309,7 @@ void TextureCacheBase::CopyRenderTargetToTexture( if (is_xfb_copy && (g_ActiveConfig.bDumpXFBTarget || g_ActiveConfig.bGraphicMods)) { - const std::string id = fmt::format("{}x{}", tex_w, tex_h); + const std::string id = fmt::format("n{:06}_{}x{}", xfb_count++, tex_w, tex_h); if (g_ActiveConfig.bGraphicMods) { entry->texture_info_name = fmt::format("{}_{}", XFB_DUMP_PREFIX, id); @@ -2387,9 +2317,8 @@ void TextureCacheBase::CopyRenderTargetToTexture( if (g_ActiveConfig.bDumpXFBTarget) { - entry->texture->Save(fmt::format("{}{}_n{:06}_{}.png", - File::GetUserPath(D_DUMPTEXTURES_IDX), XFB_DUMP_PREFIX, - xfb_count++, id), + entry->texture->Save(fmt::format("{}{}_{}.png", File::GetUserPath(D_DUMPTEXTURES_IDX), + XFB_DUMP_PREFIX, id), 0); } } @@ -2410,6 +2339,23 @@ void TextureCacheBase::CopyRenderTargetToTexture( 0); } } + + if (g_ActiveConfig.bGraphicMods) + { + GraphicsModSystem::TextureView texture; + texture.hash_name = entry->texture_info_name; + texture.texture_data = entry->texture.get(); + if (is_xfb_copy) + { + texture.texture_type = GraphicsModSystem::TextureType::XFB; + } + else + { + texture.texture_type = GraphicsModSystem::TextureType::EFB; + } + auto& mod_manager = system.GetGraphicsModManager(); + mod_manager.GetBackend().OnTextureCreate(texture); + } } } @@ -2456,6 +2402,37 @@ void TextureCacheBase::CopyRenderTargetToTexture( } } + InvalideOverlappingTextures(dstStride, dstAddr, bytes_per_row, num_blocks_y, copy_to_vram, + copy_to_ram); + + if (OpcodeDecoder::g_record_fifo_data) + { + // Mark the memory behind this efb copy as dynamicly generated for the Fifo log + u32 address = dstAddr; + for (u32 i = 0; i < num_blocks_y; i++) + { + Core::System::GetInstance().GetFifoRecorder().UseMemory(address, bytes_per_row, + MemoryUpdate::Type::TextureMap, true); + address += dstStride; + } + } + + // Even if the copy is deferred, still compute the hash. This way if the copy is used as a texture + // in a subsequent draw before it is flushed, it will have the same hash. + if (entry) + { + const u64 hash = entry->CalculateHash(); + entry->SetHashes(hash, hash); + m_textures_by_address.emplace(dstAddr, std::move(entry)); + } +} + +void TextureCacheBase::InvalideOverlappingTextures(u32 dstStride, u32 dstAddr, u32 bytes_per_row, + u32 num_blocks_y, bool copy_to_vram, + bool copy_to_ram) +{ + const u32 covered_range = num_blocks_y * dstStride; + // Invalidate all textures, if they are either fully overwritten by our efb copy, or if they // have a different stride than our efb copy. Partly overwritten textures with the same stride // as our efb copy are marked to check them for partial texture updates. @@ -2518,27 +2495,6 @@ void TextureCacheBase::CopyRenderTargetToTexture( } ++iter.first; } - - if (OpcodeDecoder::g_record_fifo_data) - { - // Mark the memory behind this efb copy as dynamicly generated for the Fifo log - u32 address = dstAddr; - for (u32 i = 0; i < num_blocks_y; i++) - { - Core::System::GetInstance().GetFifoRecorder().UseMemory(address, bytes_per_row, - MemoryUpdate::Type::TextureMap, true); - address += dstStride; - } - } - - // Even if the copy is deferred, still compute the hash. This way if the copy is used as a texture - // in a subsequent draw before it is flushed, it will have the same hash. - if (entry) - { - const u64 hash = entry->CalculateHash(); - entry->SetHashes(hash, hash); - m_textures_by_address.emplace(dstAddr, std::move(entry)); - } } void TextureCacheBase::FlushEFBCopies() @@ -2789,6 +2745,30 @@ TextureCacheBase::InvalidateTexture(TexAddrCache::iterator iter, bool discard_pe RcTcacheEntry& entry = iter->second; + if (g_ActiveConfig.bGraphicMods) + { + auto& system = Core::System::GetInstance(); + auto& mod_manager = system.GetGraphicsModManager(); + if (entry->is_efb_copy) + { + if (entry->pending_efb_copy && discard_pending_efb_copy) + { + mod_manager.GetBackend().OnTextureUnload(GraphicsModSystem::TextureType::EFB, + entry->texture_info_name); + } + } + else if (entry->is_xfb_copy) + { + mod_manager.GetBackend().OnTextureUnload(GraphicsModSystem::TextureType::XFB, + entry->texture_info_name); + } + else + { + mod_manager.GetBackend().OnTextureUnload(GraphicsModSystem::TextureType::Normal, + entry->texture_info_name); + } + } + if (entry->textures_by_hash_iter != m_textures_by_hash.end()) { m_textures_by_hash.erase(entry->textures_by_hash_iter); diff --git a/Source/Core/VideoCommon/TextureCacheBase.h b/Source/Core/VideoCommon/TextureCacheBase.h index 532feaca7d..a534d855f6 100644 --- a/Source/Core/VideoCommon/TextureCacheBase.h +++ b/Source/Core/VideoCommon/TextureCacheBase.h @@ -24,6 +24,7 @@ #include "VideoCommon/AbstractTexture.h" #include "VideoCommon/Assets/CustomAsset.h" #include "VideoCommon/BPMemory.h" +#include "VideoCommon/HiresTextures.h" #include "VideoCommon/TextureConfig.h" #include "VideoCommon/TextureDecoder.h" #include "VideoCommon/TextureInfo.h" @@ -167,8 +168,8 @@ struct TCacheEntry std::string texture_info_name = ""; - std::vector> linked_game_texture_assets; - std::vector> linked_asset_dependencies; + VideoCommon::CustomTextureData* last_custom_texture_data; + std::shared_ptr hires_texture; explicit TCacheEntry(std::unique_ptr tex, std::unique_ptr fb); @@ -345,17 +346,19 @@ private: static bool DidLinkedAssetsChange(const TCacheEntry& entry); + void InvalideOverlappingTextures(u32 dstStride, u32 dstAddr, u32 bytes_per_row, u32 num_blocks_y, + bool copy_to_vram, bool copy_to_ram); + TCacheEntry* LoadImpl(const TextureInfo& texture_info, bool force_reload); bool CreateUtilityTextures(); void SetBackupConfig(const VideoConfig& config); - RcTcacheEntry - CreateTextureEntry(const TextureCreationInfo& creation_info, const TextureInfo& texture_info, - int safety_color_sample_size, - std::vector> assets_data, - bool custom_arbitrary_mipmaps, bool skip_texture_dump); + RcTcacheEntry CreateTextureEntry(const TextureCreationInfo& creation_info, + const TextureInfo& texture_info, int safety_color_sample_size, + VideoCommon::CustomTextureData* custom_texture_data, + bool custom_arbitrary_mipmaps, bool skip_texture_dump); RcTcacheEntry GetXFBFromCache(u32 address, u32 width, u32 height, u32 stride); diff --git a/Source/Core/VideoCommon/VertexManagerBase.cpp b/Source/Core/VideoCommon/VertexManagerBase.cpp index 7987887b86..9724335761 100644 --- a/Source/Core/VideoCommon/VertexManagerBase.cpp +++ b/Source/Core/VideoCommon/VertexManagerBase.cpp @@ -6,14 +6,19 @@ #include #include #include +#include + +#include #include "Common/ChunkFile.h" #include "Common/CommonTypes.h" #include "Common/Contains.h" #include "Common/EnumMap.h" +#include "Common/EnumUtils.h" #include "Common/Logging/Log.h" #include "Common/MathUtil.h" #include "Common/SmallVector.h" +#include "Common/VariantUtil.h" #include "Core/DolphinAnalytics.h" #include "Core/HW/SystemTimers.h" @@ -25,8 +30,7 @@ #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/CameraManager.h" #include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h" #include "VideoCommon/IndexGenerator.h" #include "VideoCommon/NativeVertexFormat.h" @@ -109,6 +113,29 @@ static bool IsNormalProjection(const Projection::Raw& projection, const Viewport config.widescreen_heuristic_aspect_ratio_slop; } +static void GetTextureAndSamplerFromResource( + const GraphicsModSystem::MaterialResource::TextureLikeResource& texture_like_resource, + VideoCommon::CameraManager& camera_manager, const AbstractTexture** texture, + const SamplerState** sampler, u32* sampler_index, std::string_view* texture_hash) +{ + std::visit( + overloaded{[&](const GraphicsModSystem::TextureResource& texture_resource) { + *sampler = texture_resource.sampler; + *sampler_index = texture_resource.sampler_index; + *texture = texture_resource.texture; + *texture_hash = texture_resource.texture_hash_for_sampler; + }, + [&](const GraphicsModSystem::InputRenderTargetResource& render_target_resource) { + *sampler = render_target_resource.sampler; + *sampler_index = render_target_resource.sampler_index; + *texture = camera_manager.GetRenderTarget( + render_target_resource.camera_originating_draw_call, + render_target_resource.render_target_name); + *texture_hash = render_target_resource.texture_hash_for_sampler; + }}, + texture_like_resource); +} + VertexManagerBase::VertexManagerBase() : m_cpu_vertex_buffer(MAXVBUFFERSIZE), m_cpu_index_buffer(MAXIBUFFERSIZE) { @@ -123,8 +150,8 @@ bool VertexManagerBase::Initialize() m_after_present_event = AfterPresentEvent::Register( [this](const PresentInfo& pi) { m_ticks_elapsed = pi.emulated_timestamp; }, "VertexManagerBase"); - m_index_generator.Init(); - m_custom_shader_cache = std::make_unique(); + + m_index_generator.Init(false); m_cpu_cull.Init(); return true; } @@ -137,6 +164,14 @@ u32 VertexManagerBase::GetRemainingSize() const void VertexManagerBase::AddIndices(OpcodeDecoder::Primitive primitive, u32 num_vertices) { m_index_generator.AddIndices(primitive, num_vertices); + + if (g_ActiveConfig.bGraphicMods) + { + // Tell any graphics mod backend we had more indices + auto& system = Core::System::GetInstance(); + auto& mod_manager = system.GetGraphicsModManager(); + mod_manager.GetBackend().AddIndices(primitive, num_vertices); + } } bool VertexManagerBase::AreAllVerticesCulled(VertexLoaderBase* loader, @@ -183,6 +218,13 @@ DataReader VertexManagerBase::PrepareForAdditionalData(OpcodeDecoder::Primitive // need to alloc new buffer if (m_is_flushed) [[unlikely]] { + if (g_ActiveConfig.bGraphicMods) + { + auto& system = Core::System::GetInstance(); + auto& mod_manager = system.GetGraphicsModManager(); + mod_manager.GetBackend().ResetIndices(); + } + if (cullall) { // This buffer isn't getting sent to the GPU. Just allocate it on the cpu. @@ -336,6 +378,7 @@ void VertexManagerBase::ResetBuffer(u32 vertex_stride) m_cur_buffer_pointer = m_cpu_vertex_buffer.data(); m_end_buffer_pointer = m_base_buffer_pointer + m_cpu_vertex_buffer.size(); m_index_generator.Start(m_cpu_index_buffer.data()); + m_last_reset_pointer = m_cur_buffer_pointer; } void VertexManagerBase::CommitBuffer(u32 num_vertices, u32 vertex_stride, u32 num_indices, @@ -547,58 +590,164 @@ void VertexManagerBase::Flush() } auto& pixel_shader_manager = system.GetPixelShaderManager(); - auto& geometry_shader_manager = system.GetGeometryShaderManager(); auto& vertex_shader_manager = system.GetVertexShaderManager(); auto& xf_state_manager = system.GetXFStateManager(); - if (g_ActiveConfig.bGraphicMods) - { - const double seconds_elapsed = - static_cast(m_ticks_elapsed) / system.GetSystemTimers().GetTicksPerSecond(); - pixel_shader_manager.constants.time_ms = seconds_elapsed * 1000; - } + const auto used_textures = UsedTextures(); CalculateNormals(VertexLoaderManager::GetCurrentVertexFormat()); - // Calculate ZSlope for zfreeze - const auto used_textures = UsedTextures(); - std::vector texture_names; - Common::SmallVector texture_units; + Common::SmallVector textures; std::array samplers; if (!m_cull_all) { - if (!g_ActiveConfig.bGraphicMods) + for (const u32 i : used_textures) { - for (const u32 i : used_textures) - { - const auto cache_entry = g_texture_cache->Load(TextureInfo::FromStage(i)); - if (!cache_entry) - continue; - const float custom_tex_scale = cache_entry->GetWidth() / float(cache_entry->native_width); - samplers[i] = TextureCacheBase::GetSamplerState( - i, custom_tex_scale, cache_entry->is_custom_tex, cache_entry->has_arbitrary_mips); - } - } - else - { - for (const u32 i : used_textures) - { - const auto cache_entry = g_texture_cache->Load(TextureInfo::FromStage(i)); - if (cache_entry) - { - if (!Common::Contains(texture_names, cache_entry->texture_info_name)) - { - texture_names.push_back(cache_entry->texture_info_name); - texture_units.push_back(i); - } + const auto cache_entry = g_texture_cache->Load(TextureInfo::FromStage(i)); + if (!cache_entry) + continue; + const float custom_tex_scale = cache_entry->GetWidth() / float(cache_entry->native_width); + samplers[i] = TextureCacheBase::GetSamplerState( + i, custom_tex_scale, cache_entry->is_custom_tex, cache_entry->has_arbitrary_mips); - const float custom_tex_scale = cache_entry->GetWidth() / float(cache_entry->native_width); - samplers[i] = TextureCacheBase::GetSamplerState( - i, custom_tex_scale, cache_entry->is_custom_tex, cache_entry->has_arbitrary_mips); + if (g_ActiveConfig.bGraphicMods) + { + GraphicsModSystem::TextureView texture; + texture.hash_name = cache_entry->texture_info_name; + texture.texture_data = cache_entry->texture.get(); + if (cache_entry->is_efb_copy) + { + texture.texture_type = GraphicsModSystem::EFB; } + else if (cache_entry->is_xfb_copy) + { + texture.texture_type = GraphicsModSystem::XFB; + } + else + { + texture.texture_type = GraphicsModSystem::Normal; + } + texture.unit = i; + textures.push_back(std::move(texture)); } } } - vertex_shader_manager.SetConstants(texture_names, xf_state_manager); + + /*Common::SmallVector, 8> lights_this_draw; + if (g_ActiveConfig.bGraphicMods) + { + // Add any lights + const auto& lights_changed = xf_state_manager.GetLightsChanged(); + if (lights_changed[0] >= 0) + { + const int istart = lights_changed[0] / 0x10; + const int iend = (lights_changed[1] + 15) / 0x10; + + for (int i = istart; i < iend; ++i) + { + const Light& light = xfmem.lights[i]; + + VertexShaderConstants::Light dstlight; + + // xfmem.light.color is packed as abgr in u8[4], so we have to swap the order + dstlight.color[0] = light.color[3]; + dstlight.color[1] = light.color[2]; + dstlight.color[2] = light.color[1]; + dstlight.color[3] = light.color[0]; + + dstlight.cosatt[0] = light.cosatt[0]; + dstlight.cosatt[1] = light.cosatt[1]; + dstlight.cosatt[2] = light.cosatt[2]; + + if (fabs(light.distatt[0]) < 0.00001f && fabs(light.distatt[1]) < 0.00001f && + fabs(light.distatt[2]) < 0.00001f) + { + // dist attenuation, make sure not equal to 0!!! + dstlight.distatt[0] = .00001f; + } + else + { + dstlight.distatt[0] = light.distatt[0]; + } + dstlight.distatt[1] = light.distatt[1]; + dstlight.distatt[2] = light.distatt[2]; + + dstlight.pos[0] = light.dpos[0]; + dstlight.pos[1] = light.dpos[1]; + dstlight.pos[2] = light.dpos[2]; + + // TODO: Hardware testing is needed to confirm that this normalization is correct + auto sanitize = [](float f) { + if (std::isnan(f)) + return 0.0f; + else if (std::isinf(f)) + return f > 0.0f ? 1.0f : -1.0f; + else + return f; + }; + double norm = double(light.ddir[0]) * double(light.ddir[0]) + + double(light.ddir[1]) * double(light.ddir[1]) + + double(light.ddir[2]) * double(light.ddir[2]); + norm = 1.0 / sqrt(norm); + dstlight.dir[0] = sanitize(static_cast(light.ddir[0] * norm)); + dstlight.dir[1] = sanitize(static_cast(light.ddir[1] * norm)); + dstlight.dir[2] = sanitize(static_cast(light.ddir[2] * norm)); + + XXH3_64bits_reset_withSeed(m_hash_state_impl->m_graphics_mod_light_hash_state, + m_hash_state_impl->m_graphics_mod_hash_seed); + // XXH3_64bits_update(m_hash_state_impl->m_graphics_mod_light_hash_state, &dstlight.color, + // sizeof(int4)); + XXH3_64bits_update(m_hash_state_impl->m_graphics_mod_light_hash_state, &dstlight.cosatt, + sizeof(float4)); + XXH3_64bits_update(m_hash_state_impl->m_graphics_mod_light_hash_state, &dstlight.distatt, + sizeof(float4)); + // XXH3_64bits_update(m_hash_state_impl->m_graphics_mod_light_hash_state, &dstlight.dir, + // sizeof(float4)); + const auto light_id = GraphicsMods::LightID( + XXH3_64bits_digest(m_hash_state_impl->m_graphics_mod_light_hash_state)); + lights_this_draw.emplace_back(light_id, i); + + if (editor.IsEnabled()) + { + GraphicsModEditor::LightData light_data; + light_data.m_id = light_id; + light_data.m_create_time = std::chrono::steady_clock::now(); + light_data.m_color = dstlight.color; + light_data.m_cosatt = dstlight.cosatt; + light_data.m_dir = dstlight.dir; + light_data.m_distatt = dstlight.distatt; + light_data.m_pos = dstlight.pos; + editor.AddLightData(std::move(light_data)); + } + } + } + }*/ + vertex_shader_manager.SetConstants(xf_state_manager); + /*if (g_ActiveConfig.bGraphicMods) + { + if (editor.IsEnabled()) + { + for (const auto& light_this_draw : lights_this_draw) + { + auto& light = vertex_shader_manager.constants.lights[light_this_draw.second]; + const auto light_id = light_this_draw.first; + bool skip = false; + GraphicsModActionData::Light light_action_data{&light.color, &light.cosatt, &light.distatt, + &light.pos, &light.dir, &skip}; + for (const auto& action : editor.GetLightActions(light_id)) + { + action->OnLight(&light_action_data); + } + if (skip) + { + light.color = {}; + light.cosatt = {}; + light.distatt = {}; + light.pos = {}; + light.dir = {}; + } + } + } + }*/ if (!bpmem.genMode.zfreeze) { // Must be done after VertexShaderManager::SetConstants() @@ -612,27 +761,6 @@ void VertexManagerBase::Flush() if (!m_cull_all) { - CustomPixelShaderContents custom_pixel_shader_contents; - std::optional custom_pixel_shader; - std::vector custom_pixel_texture_names; - std::span custom_pixel_shader_uniforms; - bool skip = false; - for (size_t i = 0; i < texture_names.size(); i++) - { - GraphicsModActionData::DrawStarted draw_started{texture_units, &skip, &custom_pixel_shader, - &custom_pixel_shader_uniforms}; - for (const auto& action : g_graphics_mod_manager->GetDrawStartedActions(texture_names[i])) - { - 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_names[i]); - } - custom_pixel_shader = std::nullopt; - } - } - // Now the vertices can be flushed to the GPU. Everything following the CommitBuffer() call // must be careful to not upload any utility vertices, as the binding will be lost otherwise. const u32 num_indices = m_index_generator.GetIndexLen(); @@ -647,28 +775,31 @@ void VertexManagerBase::Flush() if (PerfQueryBase::ShouldEmulate()) g_perf_query->EnableQuery(bpmem.zcontrol.early_ztest ? PQG_ZCOMP_ZCOMPLOC : PQG_ZCOMP); - if (!skip) + UpdatePipelineConfig(); + + if (g_ActiveConfig.bGraphicMods) { - UpdatePipelineConfig(); - UpdatePipelineObject(); - if (m_current_pipeline_object) - { - const AbstractPipeline* pipeline_object = m_current_pipeline_object; - if (!custom_pixel_shader_contents.shaders.empty()) - { - if (const auto custom_pipeline = - GetCustomPipeline(custom_pixel_shader_contents, m_current_pipeline_config, - m_current_uber_pipeline_config, m_current_pipeline_object)) - { - pipeline_object = custom_pipeline; - } - } - RenderDrawCall(pixel_shader_manager, geometry_shader_manager, custom_pixel_shader_contents, - custom_pixel_shader_uniforms, m_current_primitive_type, pipeline_object); - } + GraphicsModSystem::DrawDataView draw_data; + draw_data.index_data = {m_index_generator.GetIndexDataStart(), + m_index_generator.GetIndexLen()}; + draw_data.projection_type = xfmem.projection.type; + draw_data.uid = &m_current_pipeline_config; + draw_data.vertex_data = {m_last_reset_pointer, m_index_generator.GetNumVerts()}; + draw_data.textures = std::move(textures); + draw_data.samplers = std::move(samplers); + draw_data.vertex_format = VertexLoaderManager::GetCurrentVertexFormat(); + draw_data.gpu_skinning_position_transform = vertex_shader_manager.constants.transformmatrices; + draw_data.gpu_skinning_normal_transform = vertex_shader_manager.constants.normalmatrices; + + auto& mod_manager = system.GetGraphicsModManager(); + mod_manager.GetBackend().OnDraw(draw_data, this); + } + else + { + static VideoCommon::CameraManager dummy_manager; + DrawEmulatedMesh(dummy_manager); } - // Track the total emulated state draws INCSTAT(g_stats.this_frame.num_draw_calls); // Even if we skip the draw, emulated state should still be impacted @@ -941,7 +1072,7 @@ void VertexManagerBase::UpdatePipelineObject() void VertexManagerBase::OnConfigChange() { // Reload index generator function tables in case VS expand config changed - m_index_generator.Init(); + m_index_generator.Init(false); } void VertexManagerBase::OnDraw() @@ -1059,30 +1190,59 @@ void VertexManagerBase::OnEndFrame() InvalidatePipelineObject(); } -void VertexManagerBase::NotifyCustomShaderCacheOfHostChange(const ShaderHostConfig& host_config) +void VertexManagerBase::ClearAdditionalCameras(const MathUtil::Rectangle& rc, + bool color_enable, bool alpha_enable, bool z_enable, + u32 color, u32 z) { - m_custom_shader_cache->SetHostConfig(host_config); - m_custom_shader_cache->Reload(); + if (g_ActiveConfig.bGraphicMods) + { + auto& system = Core::System::GetInstance(); + auto& mod_manager = system.GetGraphicsModManager(); + mod_manager.GetBackend().ClearAdditionalCameras(rc, color_enable, alpha_enable, z_enable, color, + z); + } } -void VertexManagerBase::RenderDrawCall( - PixelShaderManager& pixel_shader_manager, GeometryShaderManager& geometry_shader_manager, - const CustomPixelShaderContents& custom_pixel_shader_contents, - std::span custom_pixel_shader_uniforms, PrimitiveType primitive_type, - const AbstractPipeline* current_pipeline) +void VertexManagerBase::DrawEmulatedMesh(VideoCommon::CameraManager& camera_manager, + const Common::Matrix44& custom_transform) { - // Now we can upload uniforms, as nothing else will override them. - geometry_shader_manager.SetConstants(primitive_type); - pixel_shader_manager.SetConstants(); - if (!custom_pixel_shader_uniforms.empty() && - pixel_shader_manager.custom_constants.data() != custom_pixel_shader_uniforms.data()) + auto& system = Core::System::GetInstance(); + auto& geometry_shader_manager = system.GetGeometryShaderManager(); + auto& pixel_shader_manager = system.GetPixelShaderManager(); + auto& vertex_shader_manager = system.GetVertexShaderManager(); + + UpdatePipelineObject(); + + memcpy(vertex_shader_manager.constants.custom_transform.data(), custom_transform.data.data(), + 4 * sizeof(float4)); + + const auto camera_view = camera_manager.GetCurrentCameraView({}); + + if (camera_view.transform) { - pixel_shader_manager.custom_constants_dirty = true; + const u64 camera_id = Common::ToUnderlying<>(camera_view.id); + if (m_last_camera_id != camera_id) + { + vertex_shader_manager.ForceProjectionMatrixUpdate(system.GetXFStateManager(), + *camera_view.transform); + m_last_camera_id = camera_id; + } } - pixel_shader_manager.custom_constants = custom_pixel_shader_uniforms; + else + { + if (m_last_camera_id) + { + vertex_shader_manager.ForceProjectionMatrixUpdate(system.GetXFStateManager(), + Common::Matrix44::Identity()); + } + m_last_camera_id.reset(); + } + + geometry_shader_manager.SetConstants(m_current_primitive_type); + pixel_shader_manager.SetConstants(); UploadUniforms(); - g_gfx->SetPipeline(current_pipeline); + g_gfx->SetPipeline(m_current_pipeline_object); u32 base_vertex, base_index; CommitBuffer(m_index_generator.GetNumVerts(), @@ -1090,7 +1250,8 @@ void VertexManagerBase::RenderDrawCall( m_index_generator.GetIndexLen(), &base_vertex, &base_index); if (g_backend_info.api_type != APIType::D3D && g_ActiveConfig.UseVSForLinePointExpand() && - (primitive_type == PrimitiveType::Points || primitive_type == PrimitiveType::Lines)) + (m_current_primitive_type == PrimitiveType::Points || + m_current_primitive_type == PrimitiveType::Lines)) { // VS point/line expansion puts the vertex id at gl_VertexID << 2 // That means the base vertex has to be adjusted to match @@ -1099,71 +1260,280 @@ void VertexManagerBase::RenderDrawCall( } DrawCurrentBatch(base_index, m_index_generator.GetIndexLen(), base_vertex); -} -const AbstractPipeline* VertexManagerBase::GetCustomPipeline( - const CustomPixelShaderContents& custom_pixel_shader_contents, - const VideoCommon::GXPipelineUid& current_pipeline_config, - const VideoCommon::GXUberPipelineUid& current_uber_pipeline_config, - const AbstractPipeline* current_pipeline) const -{ - if (current_pipeline) + AbstractFramebuffer* frame_buffer_to_restore = nullptr; + + // Do we have any other views? If so we need to redraw with those + // frame buffers... + const auto camera_views = camera_manager.GetAdditionalViews({}); + for (const auto additional_camera_view : camera_views) { - if (!custom_pixel_shader_contents.shaders.empty()) + if (xfmem.projection.type == ProjectionType::Orthographic && + additional_camera_view.skip_orthographic) { - CustomShaderInstance custom_shaders; - custom_shaders.pixel_contents = custom_pixel_shader_contents; - switch (g_ActiveConfig.iShaderCompilationMode) - { - case ShaderCompilationMode::Synchronous: - case ShaderCompilationMode::AsynchronousSkipRendering: - { - if (auto pipeline = m_custom_shader_cache->GetPipelineAsync( - current_pipeline_config, custom_shaders, current_pipeline->m_config)) - { - return *pipeline; - } - } - break; - case ShaderCompilationMode::SynchronousUberShaders: - { - // D3D has issues compiling large custom ubershaders - // use specialized shaders instead - if (g_backend_info.api_type == APIType::D3D) - { - if (auto pipeline = m_custom_shader_cache->GetPipelineAsync( - current_pipeline_config, custom_shaders, current_pipeline->m_config)) - { - return *pipeline; - } - } - else - { - if (auto pipeline = m_custom_shader_cache->GetPipelineAsync( - current_uber_pipeline_config, custom_shaders, current_pipeline->m_config)) - { - return *pipeline; - } - } - } - break; - case ShaderCompilationMode::AsynchronousUberShaders: - { - if (auto pipeline = m_custom_shader_cache->GetPipelineAsync( - current_pipeline_config, custom_shaders, current_pipeline->m_config)) - { - return *pipeline; - } - else if (auto uber_pipeline = m_custom_shader_cache->GetPipelineAsync( - current_uber_pipeline_config, custom_shaders, current_pipeline->m_config)) - { - return *uber_pipeline; - } - } - break; - }; + continue; } + frame_buffer_to_restore = g_gfx->GetCurrentFramebuffer(); + g_gfx->SetFramebuffer(additional_camera_view.framebuffer); + if (additional_camera_view.transform) + { + const u64 camera_id = Common::ToUnderlying<>(additional_camera_view.id); + if (m_last_camera_id != camera_id) + { + vertex_shader_manager.ForceProjectionMatrixUpdate(system.GetXFStateManager(), + *additional_camera_view.transform); + m_last_camera_id = camera_id; + } + } + else + { + if (m_last_camera_id) + { + vertex_shader_manager.ForceProjectionMatrixUpdate(system.GetXFStateManager(), + Common::Matrix44::Identity()); + } + m_last_camera_id.reset(); + } + + UploadUniforms(); + DrawCurrentBatch(base_index, m_index_generator.GetIndexLen(), base_vertex); } - return nullptr; + if (frame_buffer_to_restore) + { + g_gfx->SetFramebuffer(frame_buffer_to_restore); + } +} + +void VertexManagerBase::DrawEmulatedMesh(GraphicsModSystem::MaterialResource* material_resource, + const GraphicsModSystem::DrawDataView& draw_data, + const Common::Matrix44& custom_transform, + VideoCommon::CameraManager& camera_manager) +{ + if (material_resource) + { + auto& system = Core::System::GetInstance(); + auto& vertex_shader_manager = system.GetVertexShaderManager(); + memcpy(vertex_shader_manager.constants.custom_transform.data(), custom_transform.data.data(), + 4 * sizeof(float4)); + + u32 base_vertex, base_index; + CommitBuffer(m_index_generator.GetNumVerts(), + VertexLoaderManager::GetCurrentVertexFormat()->GetVertexStride(), + m_index_generator.GetIndexLen(), &base_vertex, &base_index); + + if (g_backend_info.api_type != APIType::D3D && g_ActiveConfig.UseVSForLinePointExpand() && + (m_current_primitive_type == PrimitiveType::Points || + m_current_primitive_type == PrimitiveType::Lines)) + { + // VS point/line expansion puts the vertex id at gl_VertexID << 2 + // That means the base vertex has to be adjusted to match + // (The shader adds this after shifting right on D3D, so no need to do this) + base_vertex <<= 2; + } + + DrawViewsWithMaterial(base_vertex, base_index, m_index_generator.GetIndexLen(), + m_current_primitive_type, draw_data, material_resource, camera_manager); + } + else + { + DrawEmulatedMesh(camera_manager, custom_transform); + } +} + +void VertexManagerBase::DrawCustomMesh(GraphicsModSystem::MeshResource* mesh_resource, + const GraphicsModSystem::DrawDataView& draw_data, + const Common::Matrix44& custom_transform, + bool ignore_mesh_transform, + VideoCommon::CameraManager& camera_manager) +{ + auto& system = Core::System::GetInstance(); + auto& vertex_shader_manager = system.GetVertexShaderManager(); + + for (const auto& mesh_chunk : mesh_resource->mesh_chunks) + { + // TODO: draw with a generic material? + if (!mesh_chunk.material) [[unlikely]] + continue; + + if (!mesh_chunk.material->pipeline || !mesh_chunk.material->pipeline->m_config.vertex_shader || + !mesh_chunk.material->pipeline->m_config.pixel_shader) [[unlikely]] + continue; + + vertex_shader_manager.SetVertexFormat(mesh_chunk.components_available, + mesh_chunk.vertex_format->GetVertexDeclaration()); + + Common::Matrix44 computed_transform; + computed_transform = Common::Matrix44::Translate(mesh_resource->pivot_point) * custom_transform; + if (!ignore_mesh_transform) + { + computed_transform *= mesh_chunk.transform; + } + memcpy(vertex_shader_manager.constants.custom_transform.data(), computed_transform.data.data(), + 4 * sizeof(float4)); + + u32 base_vertex, base_index; + UploadUtilityVertices( + mesh_chunk.vertex_data.data(), mesh_chunk.vertex_stride, + static_cast(mesh_chunk.vertex_data.size()), mesh_chunk.index_data.data(), + static_cast(mesh_chunk.index_data.size()), &base_vertex, &base_index); + + DrawViewsWithMaterial(base_vertex, base_index, static_cast(mesh_chunk.index_data.size()), + mesh_chunk.primitive_type, draw_data, mesh_chunk.material, + camera_manager); + } +} + +void VertexManagerBase::DrawViewsWithMaterial( + u32 base_vertex, u32 base_index, u32 index_size, PrimitiveType primitive_type, + const GraphicsModSystem::DrawDataView& draw_data, + GraphicsModSystem::MaterialResource* material_resource, + VideoCommon::CameraManager& camera_manager) +{ + if (!material_resource) [[unlikely]] + return; + + auto& system = Core::System::GetInstance(); + auto& vertex_shader_manager = system.GetVertexShaderManager(); + + AbstractFramebuffer* frame_buffer_to_restore = nullptr; + + const auto camera_view = camera_manager.GetCurrentCameraView(material_resource->render_targets); + const auto additional_camera_views = + camera_manager.GetAdditionalViews(material_resource->render_targets); + if (camera_view.framebuffer || !additional_camera_views.empty()) + { + frame_buffer_to_restore = g_gfx->GetCurrentFramebuffer(); + } + + if (camera_view.framebuffer) + { + g_gfx->SetFramebuffer(camera_view.framebuffer); + } + + if (camera_view.transform) + { + const u64 camera_id = Common::ToUnderlying<>(camera_view.id); + if (m_last_camera_id != camera_id) + { + vertex_shader_manager.ForceProjectionMatrixUpdate(system.GetXFStateManager(), + *camera_view.transform); + m_last_camera_id = camera_id; + } + } + else + { + if (m_last_camera_id) + { + vertex_shader_manager.ForceProjectionMatrixUpdate(system.GetXFStateManager(), + Common::Matrix44::Identity()); + } + m_last_camera_id.reset(); + } + DrawWithMaterial(base_vertex, base_index, index_size, primitive_type, draw_data, + material_resource, camera_manager); + + // Do we have any other views? If so we need to redraw with the current material to those + // frame buffers... + for (const auto additional_camera_view : additional_camera_views) + { + if (xfmem.projection.type == ProjectionType::Orthographic && + additional_camera_view.skip_orthographic) + { + continue; + } + g_gfx->SetFramebuffer(additional_camera_view.framebuffer); + if (additional_camera_view.transform) + { + const u64 camera_id = Common::ToUnderlying<>(additional_camera_view.id); + if (m_last_camera_id != camera_id) + { + vertex_shader_manager.ForceProjectionMatrixUpdate(system.GetXFStateManager(), + *additional_camera_view.transform); + m_last_camera_id = camera_id; + } + } + else + { + if (m_last_camera_id) + { + vertex_shader_manager.ForceProjectionMatrixUpdate(system.GetXFStateManager(), + Common::Matrix44::Identity()); + } + m_last_camera_id.reset(); + } + DrawWithMaterial(base_vertex, base_index, index_size, primitive_type, draw_data, + material_resource, camera_manager); + } + + if (frame_buffer_to_restore) + { + g_gfx->SetFramebuffer(frame_buffer_to_restore); + } + + if (material_resource->next) + { + DrawViewsWithMaterial(base_vertex, base_index, index_size, primitive_type, draw_data, + material_resource->next, camera_manager); + } +} + +void VertexManagerBase::DrawWithMaterial(u32 base_vertex, u32 base_index, u32 index_size, + PrimitiveType primitive_type, + const GraphicsModSystem::DrawDataView& draw_data, + GraphicsModSystem::MaterialResource* material_resource, + VideoCommon::CameraManager& camera_manager) +{ + if (!material_resource) [[unlikely]] + return; + + auto& system = Core::System::GetInstance(); + auto& geometry_shader_manager = system.GetGeometryShaderManager(); + auto& pixel_shader_manager = system.GetPixelShaderManager(); + + // Now we can upload uniforms, as nothing else will override them. + geometry_shader_manager.SetConstants(primitive_type); + pixel_shader_manager.SetConstants(); + if (!material_resource->pixel_uniform_data.empty()) + { + pixel_shader_manager.custom_constants = material_resource->pixel_uniform_data; + pixel_shader_manager.custom_constants_dirty = true; + } + UploadUniforms(); + + g_gfx->SetPipeline(material_resource->pipeline); + + const std::size_t custom_sampler_index_offset = 8; + for (std::size_t i = 0; i < material_resource->textures.size(); i++) + { + auto& texture_like_resource = material_resource->textures[i]; + + const AbstractTexture* texture = nullptr; + const SamplerState* sampler = nullptr; + std::string_view texture_hash = ""; + u32 sampler_index = 0; + GetTextureAndSamplerFromResource(texture_like_resource, camera_manager, &texture, &sampler, + &sampler_index, &texture_hash); + if ((sampler == nullptr && texture_hash.empty()) || texture == nullptr) [[unlikely]] + continue; + if (!texture_hash.empty()) + { + for (const auto& texture_view : draw_data.textures) + { + if (texture_view.hash_name == texture_hash) + { + sampler = &draw_data.samplers[texture_view.unit]; + break; + } + } + } + + if (!sampler) + continue; + + g_gfx->SetTexture(sampler_index + custom_sampler_index_offset, texture); + g_gfx->SetSamplerState(sampler_index + custom_sampler_index_offset, *sampler); + } + + DrawCurrentBatch(base_index, index_size, base_vertex); } diff --git a/Source/Core/VideoCommon/VertexManagerBase.h b/Source/Core/VideoCommon/VertexManagerBase.h index 715dbee692..2f627747b1 100644 --- a/Source/Core/VideoCommon/VertexManagerBase.h +++ b/Source/Core/VideoCommon/VertexManagerBase.h @@ -3,12 +3,15 @@ #pragma once +#include #include +#include #include #include "Common/BitSet.h" #include "Common/CommonTypes.h" #include "Common/MathUtil.h" +#include "Common/Matrix.h" #include "VideoCommon/CPUCull.h" #include "VideoCommon/IndexGenerator.h" #include "VideoCommon/RenderState.h" @@ -16,7 +19,6 @@ #include "VideoCommon/VideoEvents.h" struct CustomPixelShaderContents; -class CustomShaderCache; class DataReader; class GeometryShaderManager; class NativeVertexFormat; @@ -24,6 +26,18 @@ class PixelShaderManager; class PointerWrap; struct PortableVertexDeclaration; +namespace GraphicsModSystem +{ +struct DrawDataView; +struct MaterialResource; +struct MeshResource; +} // namespace GraphicsModSystem + +namespace VideoCommon +{ +class CameraManager; +} + struct Slope { float dfdx; @@ -134,7 +148,6 @@ 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); @@ -171,7 +184,43 @@ public: // Call at the end of a frame. void OnEndFrame(); + // Passes on a clear from the game to any additional cameras + void ClearAdditionalCameras(const MathUtil::Rectangle& rc, bool color_enable, + bool alpha_enable, bool z_enable, u32 color, u32 z); + + // Draws the normal mesh sourced from emulation + void DrawEmulatedMesh(VideoCommon::CameraManager& camera_manager, + const Common::Matrix44& custom_transform = Common::Matrix44::Identity()); + + // Draws the normal mesh sourced from emulation, with a custom shader and/or transform + void DrawEmulatedMesh(GraphicsModSystem::MaterialResource* material_resource, + const GraphicsModSystem::DrawDataView& draw_data, + const Common::Matrix44& custom_transform, + VideoCommon::CameraManager& camera_manager); + + // Draw a custom mesh sourced from a mod, with a custom shader and custom vertex information + void DrawCustomMesh(GraphicsModSystem::MeshResource* mesh_resource, + const GraphicsModSystem::DrawDataView& draw_data, + const Common::Matrix44& custom_transform, bool ignore_mesh_transform, + VideoCommon::CameraManager& camera_manager); + protected: + // Draws the current mesh data with a material, taking into account any + // additional views, as well as drawing the next material in the chain + void DrawViewsWithMaterial(u32 base_vertex, u32 base_index, u32 index_size, + PrimitiveType primitive_type, + const GraphicsModSystem::DrawDataView& draw_data, + GraphicsModSystem::MaterialResource* material_resource, + VideoCommon::CameraManager& camera_manager); + + // Draws the current mesh data with a material + void DrawWithMaterial(u32 base_vertex, u32 base_index, u32 index_size, + PrimitiveType primitive_type, + const GraphicsModSystem::DrawDataView& draw_data, + GraphicsModSystem::MaterialResource* material_resource, + VideoCommon::CameraManager& camera_manager); + ; + // When utility uniforms are used, the GX uniforms need to be re-written afterwards. static void InvalidateConstants(); @@ -199,6 +248,7 @@ protected: u8* m_cur_buffer_pointer = nullptr; u8* m_base_buffer_pointer = nullptr; u8* m_end_buffer_pointer = nullptr; + u8* m_last_reset_pointer = nullptr; // Alternative buffers in CPU memory for primitives we are going to discard. std::vector m_cpu_vertex_buffer; @@ -223,19 +273,10 @@ private: // Minimum number of draws per command buffer when attempting to preempt a readback operation. static constexpr u32 MINIMUM_DRAW_CALLS_PER_COMMAND_BUFFER_FOR_READBACK = 10; - void RenderDrawCall(PixelShaderManager& pixel_shader_manager, - GeometryShaderManager& geometry_shader_manager, - const CustomPixelShaderContents& custom_pixel_shader_contents, - std::span custom_pixel_shader_uniforms, PrimitiveType primitive_type, - const AbstractPipeline* current_pipeline); void UpdatePipelineConfig(); void UpdatePipelineObject(); - const AbstractPipeline* - GetCustomPipeline(const CustomPixelShaderContents& custom_pixel_shader_contents, - const VideoCommon::GXPipelineUid& current_pipeline_config, - const VideoCommon::GXUberPipelineUid& current_uber_pipeline_confi, - const AbstractPipeline* current_pipeline) const; + std::optional m_last_camera_id; bool m_is_flushed = true; FlushStatistics m_flush_statistics = {}; @@ -248,7 +289,6 @@ private: std::vector m_scheduled_command_buffer_kicks; bool m_allow_background_execution = true; - std::unique_ptr m_custom_shader_cache; u64 m_ticks_elapsed = 0; Common::EventHook m_frame_end_event; diff --git a/Source/Core/VideoCommon/VertexShaderGen.cpp b/Source/Core/VideoCommon/VertexShaderGen.cpp index 41512cd489..56ea1f6ae4 100644 --- a/Source/Core/VideoCommon/VertexShaderGen.cpp +++ b/Source/Core/VideoCommon/VertexShaderGen.cpp @@ -406,8 +406,12 @@ ShaderCode GenerateVertexShaderCode(APIType api_type, const ShaderHostConfig& ho "float3 N2 = " I_POSNORMALMATRIX "[5].xyz;\n"); } + out.Write( + "// Multiply the position vector by the custom transform matrix\n" + "float4 pos = float4(dot(rawpos, custom_transform[0]), dot(rawpos, custom_transform[1]), " + "dot(rawpos, custom_transform[2]), dot(rawpos, custom_transform[3]));\n"); out.Write("// Multiply the position vector by the position matrix\n" - "float4 pos = float4(dot(P0, rawpos), dot(P1, rawpos), dot(P2, rawpos), 1.0);\n"); + "pos = float4(dot(P0, pos), dot(P1, pos), dot(P2, pos), 1.0);\n"); if ((uid_data->components & VB_HAS_NORMAL) == 0) out.Write("float3 rawnormal = " I_CACHED_NORMAL ".xyz;\n"); if ((uid_data->components & VB_HAS_TANGENT) == 0) @@ -510,6 +514,9 @@ ShaderCode GenerateVertexShaderCode(APIType api_type, const ShaderHostConfig& ho case TexGenType::Color1: out.Write("o.tex{}.xyz = float3(o.colors_1.x, o.colors_1.y, 1);\n", i); break; + case TexGenType::Passthrough: + out.Write("o.tex{}.xyz = float3(coord.x, coord.y, 1);\n", i); + break; case TexGenType::Regular: out.Write("o.tex{0}.xyz = dolphin_transform_texcoord{0}(coord);\n", i); break; @@ -707,3 +714,925 @@ ShaderCode GenerateVertexShaderCode(APIType api_type, const ShaderHostConfig& ho return out; } + +namespace VertexShader +{ +void WriteTransforms(APIType api_type, const ShaderHostConfig& host_config, + const vertex_shader_uid_data* uid_data, ShaderCode& out) +{ + out.Write("vec4 dolphin_transform_position(vec4 rawpos)\n"); + out.Write("{{\n"); + if ((uid_data->components & VB_HAS_POSMTXIDX) != 0) + { + // Vertex format has a per-vertex matrix + out.Write("\tint posidx = int(posmtx.r);\n" + "\tvec4 P0 = " I_TRANSFORMMATRICES "[posidx];\n" + "\tvec4 P1 = " I_TRANSFORMMATRICES "[posidx + 1];\n" + "\tvec4 P2 = " I_TRANSFORMMATRICES "[posidx + 2];\n"); + } + else + { + // One shared matrix + out.Write("\tvec4 P0 = " I_POSNORMALMATRIX "[0];\n" + "\tvec4 P1 = " I_POSNORMALMATRIX "[1];\n" + "\tvec4 P2 = " I_POSNORMALMATRIX "[2];\n"); + } + out.Write("\t// Multiply the position vector by the custom transform matrix\n" + "\tvec4 pos = vec4(dot(rawpos, custom_transform[0]), dot(rawpos, custom_transform[1]), " + "dot(rawpos, custom_transform[2]), dot(rawpos, custom_transform[3]));\n"); + out.Write("\t// Multiply the position vector by the position matrix\n" + "\treturn vec4(dot(P0, pos), dot(P1, pos), dot(P2, pos), 1.0);\n"); + out.Write("}}\n\n"); + + out.Write("vec4 dolphin_project_position(vec4 pos)\n"); + out.Write("{{\n"); + out.Write("\treturn vec4(dot(" I_PROJECTION "[0], pos), dot(" I_PROJECTION + "[1], pos), dot(" I_PROJECTION "[2], pos), dot(" I_PROJECTION "[3], pos));\n"); + out.Write("}}\n\n"); + + out.Write("vec3 dolphin_transform_normal(vec3 norm)\n"); + out.Write("{{\n"); + + if ((uid_data->components & VB_HAS_NORMAL) != 0) + { + if ((uid_data->components & VB_HAS_POSMTXIDX) != 0) + { + // Vertex format has a per-vertex matrix + out.Write("\tint posidx = int(posmtx.r);\n"); + out.Write("\tint normidx = posidx & 31;\n" + "\tvec3 N0 = " I_NORMALMATRICES "[normidx].xyz;\n" + "\tvec3 N1 = " I_NORMALMATRICES "[normidx + 1].xyz;\n" + "\tvec3 N2 = " I_NORMALMATRICES "[normidx + 2].xyz;\n"); + } + else + { + // One shared matrix + out.Write("\tvec3 N0 = " I_POSNORMALMATRIX "[3].xyz;\n" + "\tvec3 N1 = " I_POSNORMALMATRIX "[4].xyz;\n" + "\tvec3 N2 = " I_POSNORMALMATRIX "[5].xyz;\n"); + } + // The scale of the transform matrix is used to control the size of the emboss map effect, by + // changing the scale of the transformed binormals (which only get used by emboss map texgens). + // By normalising the first transformed normal (which is used by lighting calculations and needs + // to be unit length), the same transform matrix can do double duty, scaling for emboss mapping, + // and not scaling for lighting. + out.Write("\treturn normalize(vec3(dot(N0, norm), dot(N1, norm), dot(N2, " + "norm)));\n"); + } + else + { + out.Write("\treturn norm;\n"); + } + + out.Write("}}\n\n"); + + out.Write("vec3 dolphin_transform_binormal(vec3 binormal)\n"); + out.Write("{{\n"); + + if ((uid_data->components & VB_HAS_NORMAL) != 0) + { + if ((uid_data->components & VB_HAS_POSMTXIDX) != 0) + { + // Vertex format has a per-vertex matrix + out.Write("\tint posidx = int(posmtx.r);\n"); + out.Write("\tint normidx = posidx & 31;\n" + "\tvec3 N0 = " I_NORMALMATRICES "[normidx].xyz;\n" + "\tvec3 N1 = " I_NORMALMATRICES "[normidx + 1].xyz;\n" + "\tvec3 N2 = " I_NORMALMATRICES "[normidx + 2].xyz;\n"); + } + else + { + // One shared matrix + out.Write("\tvec3 N0 = " I_POSNORMALMATRIX "[3].xyz;\n" + "\tvec3 N1 = " I_POSNORMALMATRIX "[4].xyz;\n" + "\tvec3 N2 = " I_POSNORMALMATRIX "[5].xyz;\n"); + } + + // The scale of the transform matrix is used to control the size of the emboss map effect, by + // changing the scale of the transformed binormals (which only get used by emboss map texgens). + // By normalising the first transformed normal (which is used by lighting calculations and needs + // to be unit length), the same transform matrix can do double duty, scaling for emboss mapping, + // and not scaling for lighting. + out.Write("\treturn vec3(dot(N0, binormal), dot(N1, binormal), dot(N2, " + "binormal));\n"); + } + else + { + out.Write("\treturn vec3(0, 0, 0);\n"); + } + + out.Write("}}\n\n"); + + out.Write("vec3 dolphin_transform_tangent(vec3 tangent)\n"); + out.Write("{{\n"); + + if ((uid_data->components & VB_HAS_NORMAL) != 0) + { + if ((uid_data->components & VB_HAS_POSMTXIDX) != 0) + { + // Vertex format has a per-vertex matrix + out.Write("\tint posidx = int(posmtx.r);\n"); + out.Write("\tint normidx = posidx & 31;\n" + "\tvec3 N0 = " I_NORMALMATRICES "[normidx].xyz;\n" + "\tvec3 N1 = " I_NORMALMATRICES "[normidx + 1].xyz;\n" + "\tvec3 N2 = " I_NORMALMATRICES "[normidx + 2].xyz;\n"); + } + else + { + // One shared matrix + out.Write("\tvec3 N0 = " I_POSNORMALMATRIX "[3].xyz;\n" + "\tvec3 N1 = " I_POSNORMALMATRIX "[4].xyz;\n" + "\tvec3 N2 = " I_POSNORMALMATRIX "[5].xyz;\n"); + } + + // The scale of the transform matrix is used to control the size of the emboss map effect, by + // changing the scale of the transformed binormals (which only get used by emboss map texgens). + // By normalising the first transformed normal (which is used by lighting calculations and needs + // to be unit length), the same transform matrix can do double duty, scaling for emboss mapping, + // and not scaling for lighting. + out.Write("\treturn vec3(dot(N0, tangent), dot(N1, tangent), dot(N2, " + "tangent));\n"); + } + else + { + out.Write("\treturn vec3(0, 0, 0);\n"); + } + + out.Write("}}\n\n"); +} + +void WriteHeader(APIType api_type, const ShaderHostConfig& host_config, + const vertex_shader_uid_data* uid_data, ShaderCode& out, ShaderCode& input_extract) +{ + out.Write("{}", s_lighting_struct); + + // uniforms + out.Write("UBO_BINDING(std140, 2) uniform VSBlock {{\n"); + + out.Write("{}", s_shader_uniforms); + out.Write("}};\n"); + + if (uid_data->vs_expand != VSExpand::None) + { + out.Write("UBO_BINDING(std140, 4) uniform GSBlock {{\n"); + out.Write("{}", s_geometry_shader_uniforms); + out.Write("}};\n"); + + if (api_type == APIType::D3D) + { + // D3D doesn't include the base vertex in SV_VertexID + out.Write("UBO_BINDING(std140, 5) uniform DX_Constants {{\n" + " uint base_vertex;\n" + "}};\n\n"); + } + } + + out.Write("struct VS_OUTPUT {{\n"); + GenerateVSOutputMembers(out, api_type, uid_data->numTexGens, host_config, "", + ShaderStage::Vertex); + out.Write("}};\n\n"); + + WriteIsNanHeader(out, api_type); + + GenerateLightingShaderHeader(out, uid_data->lighting); + + if (uid_data->vs_expand == VSExpand::None) + { + out.Write("ATTRIBUTE_LOCATION({:s}) in float4 rawpos;\n", ShaderAttrib::Position); + if ((uid_data->components & VB_HAS_POSMTXIDX) != 0) + out.Write("ATTRIBUTE_LOCATION({:s}) in uint4 posmtx;\n", ShaderAttrib::PositionMatrix); + if ((uid_data->components & VB_HAS_NORMAL) != 0) + out.Write("ATTRIBUTE_LOCATION({:s}) in float3 rawnormal;\n", ShaderAttrib::Normal); + if ((uid_data->components & VB_HAS_TANGENT) != 0) + out.Write("ATTRIBUTE_LOCATION({:s}) in float3 rawtangent;\n", ShaderAttrib::Tangent); + if ((uid_data->components & VB_HAS_BINORMAL) != 0) + out.Write("ATTRIBUTE_LOCATION({:s}) in float3 rawbinormal;\n", ShaderAttrib::Binormal); + + if ((uid_data->components & VB_HAS_COL0) != 0) + out.Write("ATTRIBUTE_LOCATION({:s}) in float4 rawcolor0;\n", ShaderAttrib::Color0); + if ((uid_data->components & VB_HAS_COL1) != 0) + out.Write("ATTRIBUTE_LOCATION({:s}) in float4 rawcolor1;\n", ShaderAttrib::Color1); + + for (u32 i = 0; i < 8; ++i) + { + const u32 has_texmtx = (uid_data->components & (VB_HAS_TEXMTXIDX0 << i)); + + if ((uid_data->components & (VB_HAS_UV0 << i)) != 0 || has_texmtx != 0) + { + out.Write("ATTRIBUTE_LOCATION({:s}) in float{} rawtex{};\n", ShaderAttrib::TexCoord0 + i, + has_texmtx != 0 ? 3 : 2, i); + } + } + } + else + { + // Can't use float3, etc because we want 4-byte alignment + out.Write( + "uint4 unpack_ubyte4(uint value) {{\n" + " return uint4(value & 0xffu, (value >> 8) & 0xffu, (value >> 16) & 0xffu, value >> 24);\n" + "}}\n\n" + "struct InputData {{\n"); + if (uid_data->components & VB_HAS_POSMTXIDX) + { + out.Write(" uint posmtx;\n"); + input_extract.Write("uint4 posmtx = unpack_ubyte4(i.posmtx);\n"); + } + if (uid_data->position_has_3_elems) + { + out.Write(" float pos0;\n" + " float pos1;\n" + " float pos2;\n"); + input_extract.Write("float4 rawpos = float4(i.pos0, i.pos1, i.pos2, 1.0f);\n"); + } + else + { + out.Write(" float pos0;\n" + " float pos1;\n"); + input_extract.Write("float4 rawpos = float4(i.pos0, i.pos1, 0.0f, 1.0f);\n"); + } + std::array names = {"normal", "binormal", "tangent"}; + for (int i = 0; i < 3; i++) + { + if (uid_data->components & (VB_HAS_NORMAL << i)) + { + out.Write(" float {0}0;\n" + " float {0}1;\n" + " float {0}2;\n", + names[i]); + input_extract.Write("float3 raw{0} = float3(i.{0}0, i.{0}1, i.{0}2);\n", names[i]); + } + } + for (int i = 0; i < 2; i++) + { + if (uid_data->components & (VB_HAS_COL0 << i)) + { + out.Write(" uint color{};\n", i); + input_extract.Write("float4 rawcolor{0} = float4(unpack_ubyte4(i.color{0})) / 255.0f;\n", + i); + } + } + for (int i = 0; i < 8; i++) + { + if (uid_data->components & (VB_HAS_UV0 << i)) + { + u32 ncomponents = (uid_data->texcoord_elem_count >> (2 * i)) & 3; + if (ncomponents < 2) + { + out.Write(" float tex{};\n", i); + input_extract.Write("float3 rawtex{0} = float3(i.tex{0}, 0.0f, 0.0f);\n", i); + } + else if (ncomponents == 2) + { + out.Write(" float tex{0}_0;\n" + " float tex{0}_1;\n", + i); + input_extract.Write("float3 rawtex{0} = float3(i.tex{0}_0, i.tex{0}_1, 0.0f);\n", i); + } + else + { + out.Write(" float tex{0}_0;\n" + " float tex{0}_1;\n" + " float tex{0}_2;\n", + i); + input_extract.Write("float3 rawtex{0} = float3(i.tex{0}_0, i.tex{0}_1, i.tex{0}_2);\n", + i); + } + } + } + out.Write("}};\n\n" + "SSBO_BINDING(1) readonly restrict buffer InputBuffer {{\n" + " InputData input_buffer[];\n" + "}};\n\n"); + } + + const bool msaa = host_config.msaa; + const bool ssaa = host_config.ssaa; + const bool per_pixel_lighting = g_ActiveConfig.bEnablePixelLighting; + + if (host_config.backend_geometry_shaders) + { + out.Write("VARYING_LOCATION(0) out VertexData {{\n"); + GenerateVSOutputMembers(out, api_type, uid_data->numTexGens, host_config, + GetInterpolationQualifier(msaa, ssaa, true, false), + ShaderStage::Vertex); + out.Write("}} vs;\n"); + } + else + { + // Let's set up attributes + u32 counter = 0; + out.Write("VARYING_LOCATION({}) {} out float4 colors_0;\n", counter++, + GetInterpolationQualifier(msaa, ssaa)); + out.Write("VARYING_LOCATION({}) {} out float4 colors_1;\n", counter++, + GetInterpolationQualifier(msaa, ssaa)); + for (u32 i = 0; i < uid_data->numTexGens; ++i) + { + out.Write("VARYING_LOCATION({}) {} out float3 tex{};\n", counter++, + GetInterpolationQualifier(msaa, ssaa), i); + } + if (!host_config.fast_depth_calc) + { + out.Write("VARYING_LOCATION({}) {} out float4 clipPos;\n", counter++, + GetInterpolationQualifier(msaa, ssaa)); + } + if (per_pixel_lighting) + { + out.Write("VARYING_LOCATION({}) {} out float3 Normal;\n", counter++, + GetInterpolationQualifier(msaa, ssaa)); + out.Write("VARYING_LOCATION({}) {} out float3 WorldPos;\n", counter++, + GetInterpolationQualifier(msaa, ssaa)); + } + } + + // Write the transforms after so they can use the position matrix if it's available + // or should the functions take the posmtx too? + WriteTransforms(api_type, host_config, uid_data, out); + WriteTexCoordTransforms(api_type, host_config, uid_data, out); + + // TODO: move... + out.Write("vec4 dolphin_pixel_correction(vec4 pos)\n"); + out.Write("{{\n"); + + // Write the true depth value. If the game uses depth textures, then the pixel shader will + // override it with the correct values if not then early z culling will improve speed. + // There are two different ways to do this, when the depth range is oversized, we process + // the depth range in the vertex shader, if not we let the host driver handle it. + // + // Adjust z for the depth range. We're using an equation which incorperates a depth inversion, + // so we can map the console -1..0 range to the 0..1 range used in the depth buffer. + // We have to handle the depth range in the vertex shader instead of after the perspective + // divide, because some games will use a depth range larger than what is allowed by the + // graphics API. These large depth ranges will still be clipped to the 0..1 range, so these + // games effectively add a depth bias to the values written to the depth buffer. + out.Write("\tpos.z = pos.w * " I_PIXELCENTERCORRECTION ".w - " + "\tpos.z * " I_PIXELCENTERCORRECTION ".z;\n"); + + if (!host_config.backend_clip_control) + { + // If the graphics API doesn't support a depth range of 0..1, then we need to map z to + // the -1..1 range. Unfortunately we have to use a substraction, which is a lossy + // floating-point operation that can introduce a round-trip error. + out.Write("\tpos.z = pos.z * 2.0 - pos.w;\n"); + } + + // Correct for negative viewports by mirroring all vertices. We need to negate the height here, + // since the viewport height is already negated by the render backend. + out.Write("\tpos.xy *= sign(" I_PIXELCENTERCORRECTION ".xy * float2(1.0, -1.0));\n"); + + // The console GPU places the pixel center at 7/12 in screen space unless + // antialiasing is enabled, while D3D and OpenGL place it at 0.5. This results + // in some primitives being placed one pixel too far to the bottom-right, + // which in turn can be critical if it happens for clear quads. + // Hence, we compensate for this pixel center difference so that primitives + // get rasterized correctly. + out.Write("\tpos.xy = pos.xy - pos.w * " I_PIXELCENTERCORRECTION ".xy;\n"); + + const bool vertex_rounding = host_config.vertex_rounding; + if (vertex_rounding) + { + // By now our position is in clip space + // however, higher resolutions than the Wii outputs + // cause an additional pixel offset + // due to a higher pixel density + // we need to correct this by converting our + // clip-space position into the Wii's screen-space + // acquire the right pixel and then convert it back + out.Write("\tif (pos.w == 1.0f)\n" + "\t{{\n" + + "\t\tfloat ss_pixel_x = ((pos.x + 1.0f) * (" I_VIEWPORT_SIZE ".x * 0.5f));\n" + "\t\tfloat ss_pixel_y = ((pos.y + 1.0f) * (" I_VIEWPORT_SIZE ".y * 0.5f));\n" + + "\t\tss_pixel_x = round(ss_pixel_x);\n" + "\t\tss_pixel_y = round(ss_pixel_y);\n" + + "\t\tpos.x = ((ss_pixel_x / (" I_VIEWPORT_SIZE ".x * 0.5f)) - 1.0f);\n" + "\t\tpos.y = ((ss_pixel_y / (" I_VIEWPORT_SIZE ".y * 0.5f)) - 1.0f);\n" + "\t}}\n"); + } + + out.Write("\treturn pos;\n"); + + out.Write("}}\n"); +} + +void WriteEmulatedVertexBodyHeader(APIType api_type, const ShaderHostConfig& host_config, + const vertex_shader_uid_data* uid_data, ShaderCode& out) +{ + constexpr std::string_view emulated_vertex_definition = + "void dolphin_emulated_vertex(in DolphinVertexInput vertex_input, out DolphinVertexOutput " + "vertex_output)"; + out.Write("{}\n", emulated_vertex_definition); + out.Write("{{\n"); + + WriteVertexBody(api_type, host_config, uid_data, out); + + out.Write("}}\n"); +} + +void WriteVertexStructs(APIType api_type, const ShaderHostConfig& host_config, + const vertex_shader_uid_data* uid_data, ShaderCode& out) +{ + out.Write("struct DolphinVertexInput\n"); + out.Write("{{\n"); + out.Write("\tvec4 color_0;\n"); + out.Write("\tvec4 color_1;\n"); + out.Write("\tvec4 position;\n"); + out.Write("\tvec3 normal;\n"); + out.Write("\tvec3 binormal;\n"); + out.Write("\tvec3 tangent;\n"); + for (u32 i = 0; i < uid_data->numTexGens; i++) + { + out.Write("\tvec4 texture_coord_{};\n", i); + } + for (u32 i = uid_data->numTexGens; i < 8; i++) + { + out.Write("\tvec4 texture_coord_{};\n", i); + } + out.Write("}};\n\n"); + + out.Write("struct DolphinVertexOutput\n"); + out.Write("{{\n"); + out.Write("\tvec4 color_0;\n"); + out.Write("\tvec4 color_1;\n"); + out.Write("\tvec4 position;\n"); + out.Write("\tvec3 normal;\n"); + for (u32 i = 0; i < uid_data->numTexGens; i++) + { + out.Write("\tvec3 texture_coord_{};\n", i); + } + for (u32 i = uid_data->numTexGens; i < 8; i++) + { + out.Write("\tvec3 texture_coord_{};\n", i); + } + out.Write("}};\n\n"); +} + +void WriteVertexDefines(APIType, const ShaderHostConfig&, const vertex_shader_uid_data* uid_data, + ShaderCode& out) +{ + if ((uid_data->components & VB_HAS_COL0) != 0) + { + out.Write("#define HAS_COLOR_0 1\n"); + } + + if ((uid_data->components & VB_HAS_COL1) != 0) + { + out.Write("#define HAS_COLOR_1 1\n"); + } + + if ((uid_data->components & VB_HAS_NORMAL) != 0) + { + out.Write("#define HAS_NORMAL 1\n"); + } + + if ((uid_data->components & VB_HAS_BINORMAL) != 0) + { + out.Write("#define HAS_BINORMAL 1\n"); + } + + if ((uid_data->components & VB_HAS_TANGENT) != 0) + { + out.Write("#define HAS_TANGENT 1\n"); + } + + for (u32 i = uid_data->numTexGens; i < 8; i++) + { + if ((uid_data->components & (VB_HAS_UV0 << i)) != 0) + { + out.Write("#define HAS_TEXTURE_COORD_{} 1\n", i); + } + } +} + +void WriteVertexBody(APIType api_type, const ShaderHostConfig& host_config, + const vertex_shader_uid_data* uid_data, ShaderCode& out) +{ + out.Write("\tvertex_output.position = dolphin_transform_position(vertex_input.position);\n"); + + if ((uid_data->components & VB_HAS_NORMAL) != 0) + { + out.Write("\tvertex_output.normal = dolphin_transform_normal(vertex_input.normal);\n"); + } + else + { + out.Write("\tvertex_output.normal = vec3(0, 0, 0);\n"); + } + + const bool has_color0_texture_coordinate = + std::ranges::any_of(uid_data->texMtxInfo, [](const auto& texinfo) { + return texinfo.texgentype == TexGenType::Color0; + }); + + const bool has_color1_texture_coordinate = + std::ranges::any_of(uid_data->texMtxInfo, [](const auto& texinfo) { + return texinfo.texgentype == TexGenType::Color1; + }); + + const bool per_pixel_lighting = host_config.per_pixel_lighting; + if (per_pixel_lighting) + { + // When per-pixel lighting is enabled, the vertex colors are passed through + // unmodified so we can evaluate the lighting in the pixel shader. + out.Write("\tvertex_output.color_0 = vertex_input.color_0;\n"); + out.Write("\tvertex_output.color_1 = vertex_input.color_1;\n"); + // Note that the numColorChans logic is performed in the pixel shader. + + // We may still need to calculate the lighting per vertex if the vertex + // shader generates texture coordinates with this information + if (has_color0_texture_coordinate) + { + out.Write("\tvec4 vertex_lighting_0 = dolphin_calculate_lighting_chn0(vertex_input.color_0, " + "vertex_input.position, " + "vertex_input.normal);\n"); + } + if (has_color1_texture_coordinate) + { + out.Write("\tvec4 vertex_lighting_1 = dolphin_calculate_lighting_chn1(vertex_input.color_1, " + "vertex_input.position, " + "vertex_input.normal);\n"); + } + } + else + { + if (uid_data->numColorChans > 0) + { + out.Write("\tvec4 vertex_lighting_0 = dolphin_calculate_lighting_chn0(vertex_input.color_0, " + "vertex_input.position, " + "vertex_input.normal);\n"); + out.Write("\tvertex_output.color_0 = vertex_lighting_0;\n"); + } + else + { + if (has_color0_texture_coordinate) + { + out.Write("\tvec4 vertex_lighting_0 = " + "dolphin_calculate_lighting_chn0(vertex_input.color_0, vertex_input.position," + "vertex_input.normal);\n"); + } + } + + if (uid_data->numColorChans == 2) + { + out.Write("\tvec4 vertex_lighting_1 = dolphin_calculate_lighting_chn1(vertex_input.color_1, " + "vertex_input.position, " + "vertex_input.normal);\n"); + out.Write("\tvertex_output.color_1 = vertex_lighting_1;\n"); + } + else + { + if (has_color1_texture_coordinate) + { + out.Write("\tvec4 vertex_lighting_1 = " + "dolphin_calculate_lighting_chn1(vertex_input.color_1, vertex_input.position," + "normal);\n"); + } + } + } + + for (u32 i = 0; i < uid_data->numTexGens; ++i) + { + auto& texinfo = uid_data->texMtxInfo[i]; + + switch (texinfo.texgentype) + { + case TexGenType::EmbossMap: // calculate tex coords into bump map + + // transform the light dir into tangent space + out.Write("\tvec3 ldir = normalize(" LIGHT_POS ".xyz - vertex_input.position.xyz);\n", + LIGHT_POS_PARAMS(texinfo.embosslightshift)); + + if ((uid_data->components & VB_HAS_TANGENT) == 0) + out.Write("\tvec3 rawtangent = " I_CACHED_TANGENT ".xyz;\n"); + else + out.Write("\tvec3 rawtangent = vertex_input.tangent;\n"); + + if ((uid_data->components & VB_HAS_BINORMAL) == 0) + out.Write("\tvec3 rawbinormal = " I_CACHED_BINORMAL ".xyz;\n"); + else + out.Write("\tvec3 rawbinormal = vertex_input.binormal;\n"); + + out.Write("\tvertex_output.texture_coord_{}.xyz = vertex_output.texture_coord_{}.xyz + " + "vec3(dot(ldir, " + "dolphin_transform_tangent(rawtangent)), " + "dot(ldir, dolphin_transform_binormal(rawbinormal)), 0.0);\n", + i, texinfo.embosssourceshift); + + break; + case TexGenType::Color0: + out.Write("\tvertex_output.texture_coord_{}.xyz = vec3(vertex_lighting_0.x, " + "vertex_lighting_0.y, 1);\n", + i); + break; + case TexGenType::Color1: + out.Write("\tvertex_output.texture_coord_{}.xyz = vec3(vertex_lighting_1.x, " + "vertex_lighting_1.y, 1);\n", + i); + break; + case TexGenType::Regular: + out.Write("\tvertex_output.texture_coord_{0} = " + "dolphin_transform_texcoord{0}(vertex_input.texture_coord_{0});\n", + i); + break; + case TexGenType::Passthrough: + out.Write("vertex_output.texture_coord_{0}.xyz = vec3(vertex_input.texture_coord_{0}.x, " + "vertex_input.texture_coord_{0}.y, 1);\n", + i); + break; + }; + } + + // Fill out output that is unused + for (u32 i = uid_data->numTexGens; i < 8; i++) + { + out.Write("\tvertex_output.texture_coord_{0} = vec3(0, 0, 0);\n", i); + } +} + +ShaderCode WriteFullShader(APIType api_type, const ShaderHostConfig& host_config, + const vertex_shader_uid_data* uid_data, std::string_view custom_vertex, + std::string_view custom_uniforms) +{ + ShaderCode out; + + const bool per_pixel_lighting = g_ActiveConfig.bEnablePixelLighting; + + ShaderCode input_extract; + + WriteHeader(api_type, host_config, uid_data, out, input_extract); + + WriteVertexStructs(api_type, host_config, uid_data, out); + WriteVertexDefines(api_type, host_config, uid_data, out); + + if (!custom_uniforms.empty()) + { + out.Write("UBO_BINDING(std140, 3) uniform CustomShaderBlock {{\n"); + out.Write("{}", custom_uniforms); + out.Write("}} custom_uniforms;\n"); + } + + WriteEmulatedVertexBodyHeader(api_type, host_config, uid_data, out); + + if (custom_vertex.empty()) + { + out.Write("{}\n", vertex_definition); + out.Write("{{\n"); + + out.Write("\tdolphin_emulated_vertex(vertex_input, vertex_output);\n"); + + out.Write("}}\n"); + } + else + { + out.Write("{}\n", custom_vertex); + } + + out.Write("void main()\n{{\n"); + + if (uid_data->vs_expand != VSExpand::None) + { + out.Write("\tbool is_bottom = (gl_VertexID & 2) != 0;\n" + "\tbool is_right = (gl_VertexID & 1) != 0;\n"); + // D3D doesn't include the base vertex in SV_VertexID + // See comment in UberShaderVertex for details + if (api_type == APIType::D3D) + out.Write("\tuint vertex_id = (gl_VertexID >> 2) + base_vertex;\n"); + else + out.Write("\tuint vertex_id = uint(gl_VertexID) >> 2u;\n"); + out.Write("\tInputData i = input_buffer[vertex_id];\n" + "{}", + input_extract.GetBuffer()); + } + + out.Write("\tVS_OUTPUT o;\n"); + + // xfmem.numColorChans controls the number of color channels available to TEV, but we still need + // to generate all channels here, as it can be used in texgen. Cel-damage is an example of this. + out.Write("\tvec4 vertex_color_0, vertex_color_1;\n"); + + // To use color 1, the vertex descriptor must have color 0 and 1. + // If color 1 is present but not color 0, it is used for lighting channel 0. + const bool use_color_1 = + (uid_data->components & (VB_HAS_COL0 | VB_HAS_COL1)) == (VB_HAS_COL0 | VB_HAS_COL1); + for (u32 color = 0; color < NUM_XF_COLOR_CHANNELS; color++) + { + if ((color == 0 || use_color_1) && (uid_data->components & (VB_HAS_COL0 << color)) != 0) + { + // Use color0 for channel 0, and color1 for channel 1 if both colors 0 and 1 are present. + out.Write("\tvertex_color_{0} = rawcolor{0};\n", color); + } + else if (color == 0 && (uid_data->components & VB_HAS_COL1) != 0) + { + // Use color1 for channel 0 if color0 is not present. + out.Write("\tvertex_color_{} = rawcolor1;\n", color); + } + else + { + out.Write("\tvertex_color_{0} = missing_color_value;\n", color); + } + } + + out.Write("\tDolphinVertexInput vertex_input;\n"); + out.Write("\tvertex_input.color_0 = vertex_color_0;\n"); + out.Write("\tvertex_input.color_1 = vertex_color_1;\n"); + out.Write("\tvertex_input.position = rawpos;\n"); + + if ((uid_data->components & VB_HAS_NORMAL) != 0) + { + out.Write("\tvertex_input.normal = rawnormal;\n"); + } + else + { + out.Write("\tvertex_input.normal = vec3(0, 0, 0);\n"); + } + + if ((uid_data->components & VB_HAS_BINORMAL) != 0) + { + out.Write("\tvertex_input.binormal = rawbinormal;\n"); + } + else + { + out.Write("\tvertex_input.binormal = vec3(0, 0, 0);\n"); + } + + if ((uid_data->components & VB_HAS_TANGENT) != 0) + { + out.Write("\tvertex_input.tangent = rawtangent;\n"); + } + else + { + out.Write("\tvertex_input.tangent = vec3(0, 0, 0);\n"); + } + + for (u32 i = 0; i < uid_data->numTexGens; ++i) + { + auto& texinfo = uid_data->texMtxInfo[i]; + + out.Write("\t{{\n"); + out.Write("\t\tvec4 coord = vec4(0.0, 0.0, 1.0, 1.0);\n"); + switch (texinfo.sourcerow) + { + case SourceRow::Geom: + out.Write("\t\tcoord.xyz = rawpos.xyz;\n"); + break; + case SourceRow::Normal: + if ((uid_data->components & VB_HAS_NORMAL) != 0) + { + out.Write("\t\tcoord.xyz = rawnormal.xyz;\n"); + } + break; + case SourceRow::Colors: + ASSERT(texinfo.texgentype == TexGenType::Color0 || texinfo.texgentype == TexGenType::Color1); + break; + case SourceRow::BinormalT: + if ((uid_data->components & VB_HAS_TANGENT) != 0) + { + out.Write("\t\tcoord.xyz = rawtangent.xyz;\n"); + } + break; + case SourceRow::BinormalB: + if ((uid_data->components & VB_HAS_BINORMAL) != 0) + { + out.Write("\t\tcoord.xyz = rawbinormal.xyz;\n"); + } + break; + default: + ASSERT(texinfo.sourcerow >= SourceRow::Tex0 && texinfo.sourcerow <= SourceRow::Tex7); + u32 texnum = static_cast(texinfo.sourcerow) - static_cast(SourceRow::Tex0); + if ((uid_data->components & (VB_HAS_UV0 << (texnum))) != 0) + { + out.Write("\t\tcoord = vec4(rawtex{}.x, rawtex{}.y, 1.0, 1.0);\n", texnum, texnum); + } + break; + } + // Input form of AB11 sets z element to 1.0 + + if (texinfo.inputform == TexInputForm::AB11) + out.Write("\t\tcoord.z = 1.0;\n"); + + // Convert NaNs to 1 - needed to fix eyelids in Shadow the Hedgehog during cutscenes + // See https://bugs.dolphin-emu.org/issues/11458 + out.Write("\t\t// Convert NaN to 1\n"); + out.Write("\t\tif (dolphin_isnan(coord.x)) coord.x = 1.0;\n"); + out.Write("\t\tif (dolphin_isnan(coord.y)) coord.y = 1.0;\n"); + out.Write("\t\tif (dolphin_isnan(coord.z)) coord.z = 1.0;\n"); + + out.Write("\t\tvertex_input.texture_coord_{0} = coord;\n", i); + out.Write("\t}}\n"); + } + + // Initialize other texture coordinates that are unused + for (u32 i = uid_data->numTexGens; i < 8; i++) + { + out.Write("\tvertex_input.texture_coord_{0} = vec4(0, 0, 0, 0);\n", i); + } + + out.Write("\tDolphinVertexOutput vertex_output;\n"); + out.Write("\tvertex(vertex_input, vertex_output);\n"); + + if (per_pixel_lighting) + { + out.Write("\to.Normal = vertex_output.normal;\n"); + + // TODO: Rename, this is actually in Viewspace... + out.Write("\to.WorldPos = vertex_output.position.xyz;\n"); + } + + out.Write("\to.pos = dolphin_project_position(vertex_output.position);\n"); + + for (u32 i = 0; i < uid_data->numTexGens; ++i) + { + out.Write("\to.tex{0} = vertex_output.texture_coord_{0};\n", i); + } + + if (uid_data->numColorChans > 0) + { + out.Write("\to.colors_0 = vertex_output.color_0;\n"); + } + else + { + // The number of colors available to TEV is determined by numColorChans. + // We have to provide the fields to match the interface, so set to zero if it's not enabled. + out.Write("\to.colors_0 = vec4(0.0, 0.0, 0.0, 0.0);\n"); + } + + if (uid_data->numColorChans == 2) + { + out.Write("\to.colors_1 = vertex_output.color_1;\n"); + } + else + { + // The number of colors available to TEV is determined by numColorChans. + // We have to provide the fields to match the interface, so set to zero if it's not enabled. + out.Write("\to.colors_1 = vec4(0.0, 0.0, 0.0, 0.0);\n"); + } + + // clipPos/w needs to be done in pixel shader, not here + if (!host_config.fast_depth_calc) + out.Write("\to.clipPos = o.pos;\n"); + + // If we can disable the incorrect depth clipping planes using depth clamping, then we can do + // our own depth clipping and calculate the depth range before the perspective divide if + // necessary. + if (host_config.backend_depth_clamp) + { + // Since we're adjusting z for the depth range before the perspective divide, we have to do + // our own clipping. We want to clip so that -w <= z <= 0, which matches the console -1..0 + // range. We adjust our depth value for clipping purposes to match the perspective projection + // in the software backend, which is a hack to fix Sonic Adventure and Unleashed games. + out.Write("\tfloat clipDepth = o.pos.z * (1.0 - 1e-7);\n" + "\tfloat clipDist0 = clipDepth + o.pos.w;\n" // Near: z < -w + "\tfloat clipDist1 = -clipDepth;\n"); // Far: z > 0 + + if (host_config.backend_geometry_shaders) + { + out.Write("\to.clipDist0 = clipDist0;\n" + "\to.clipDist1 = clipDist1;\n"); + } + } + else + { + // Same depth adjustment for Sonic. Without depth clamping, it unfortunately + // affects non-clipping uses of depth too. + out.Write("\to.pos.z = o.pos.z * (1.0 - 1e-7);\n"); + } + + out.Write("\to.pos = dolphin_pixel_correction(o.pos);\n"); + + if (host_config.backend_geometry_shaders) + { + AssignVSOutputMembers(out, "vs", "o", uid_data->numTexGens, host_config); + } + else + { + // TODO: Pass interface blocks between shader stages even if geometry shaders + // are not supported, however that will require at least OpenGL 3.2 support. + for (u32 i = 0; i < uid_data->numTexGens; ++i) + out.Write("\ttex{}.xyz = o.tex{};\n", i, i); + if (!host_config.fast_depth_calc) + out.Write("\tclipPos = o.clipPos;\n"); + if (per_pixel_lighting) + { + out.Write("\tNormal = o.Normal;\n" + "\tWorldPos = o.WorldPos;\n"); + } + out.Write("\tcolors_0 = o.colors_0;\n" + "\tcolors_1 = o.colors_1;\n"); + } + + if (host_config.backend_depth_clamp) + { + out.Write("\tgl_ClipDistance[0] = clipDist0;\n" + "\tgl_ClipDistance[1] = clipDist1;\n"); + } + + // Vulkan NDC space has Y pointing down (right-handed NDC space). + if (api_type == APIType::Vulkan) + out.Write("\tgl_Position = float4(o.pos.x, -o.pos.y, o.pos.z, o.pos.w);\n"); + else + out.Write("\tgl_Position = o.pos;\n"); + out.Write("}}\n"); + + return out; +} +} // namespace VertexShader diff --git a/Source/Core/VideoCommon/VertexShaderGen.h b/Source/Core/VideoCommon/VertexShaderGen.h index 94f2a170c9..74976950bf 100644 --- a/Source/Core/VideoCommon/VertexShaderGen.h +++ b/Source/Core/VideoCommon/VertexShaderGen.h @@ -91,3 +91,19 @@ using VertexShaderUid = ShaderUid; VertexShaderUid GetVertexShaderUid(); ShaderCode GenerateVertexShaderCode(APIType api_type, const ShaderHostConfig& host_config, const vertex_shader_uid_data* uid_data); + +namespace VertexShader +{ +constexpr std::string_view vertex_definition = + "void vertex(in DolphinVertexInput vertex_input, out DolphinVertexOutput vertex_output)"; + +void WriteVertexStructs(APIType api_type, const ShaderHostConfig& host_config, + const vertex_shader_uid_data* uid_data, ShaderCode& out); +void WriteVertexDefines(APIType api_type, const ShaderHostConfig& host_config, + const vertex_shader_uid_data* uid_data, ShaderCode& out); +void WriteVertexBody(APIType api_type, const ShaderHostConfig& host_config, + const vertex_shader_uid_data* uid_data, ShaderCode& out); +ShaderCode WriteFullShader(APIType api_type, const ShaderHostConfig& host_config, + const vertex_shader_uid_data* uid_data, std::string_view custom_vertex, + std::string_view custom_uniforms); +} // namespace VertexShader diff --git a/Source/Core/VideoCommon/VertexShaderManager.cpp b/Source/Core/VideoCommon/VertexShaderManager.cpp index 976cff00f6..16c69f92e4 100644 --- a/Source/Core/VideoCommon/VertexShaderManager.cpp +++ b/Source/Core/VideoCommon/VertexShaderManager.cpp @@ -12,13 +12,12 @@ #include "Common/CommonTypes.h" #include "Common/Logging/Log.h" #include "Common/Matrix.h" +#include "Core/System.h" #include "VideoCommon/BPFunctions.h" #include "VideoCommon/BPMemory.h" #include "VideoCommon/CPMemory.h" #include "VideoCommon/FramebufferManager.h" #include "VideoCommon/FreeLookCamera.h" -#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionData.h" -#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h" #include "VideoCommon/Statistics.h" #include "VideoCommon/VertexManagerBase.h" #include "VideoCommon/VideoCommon.h" @@ -29,7 +28,6 @@ void VertexShaderManager::Init() { // Initialize state tracking variables - m_projection_graphics_mod_change = false; constants = {}; @@ -124,11 +122,21 @@ void VertexShaderManager::SetProjectionMatrix(XFStateManager& xf_state_manager) if (xf_state_manager.DidProjectionChange() || g_freelook_camera.GetController()->IsDirty()) { xf_state_manager.ResetProjection(); - auto corrected_matrix = LoadProjectionMatrix(); + auto corrected_matrix = LoadProjectionMatrix() * m_last_camera_modifier; memcpy(constants.projection.data(), corrected_matrix.data.data(), 4 * sizeof(float4)); } } +void VertexShaderManager::ForceProjectionMatrixUpdate(XFStateManager& xf_state_manager, + const Common::Matrix44& modifier) +{ + m_last_camera_modifier = modifier; + xf_state_manager.ResetProjection(); + auto corrected_matrix = LoadProjectionMatrix() * m_last_camera_modifier; + memcpy(constants.projection.data(), corrected_matrix.data.data(), 4 * sizeof(float4)); + dirty = true; +} + bool VertexShaderManager::UseVertexDepthRange() { // We can't compute the depth range in the vertex shader if we don't support depth clamp. @@ -156,8 +164,7 @@ bool VertexShaderManager::UseVertexDepthRange() // Syncs the shader constant buffers with xfmem // TODO: A cleaner way to control the matrices without making a mess in the parameters field -void VertexShaderManager::SetConstants(const std::vector& textures, - XFStateManager& xf_state_manager) +void VertexShaderManager::SetConstants(XFStateManager& xf_state_manager) { if (constants.missing_color_hex != g_ActiveConfig.iMissingColorValue) { @@ -387,37 +394,11 @@ void VertexShaderManager::SetConstants(const std::vector& textures, g_stats.AddScissorRect(); } - std::vector projection_actions; - if (g_ActiveConfig.bGraphicMods) - { - for (const auto& action : g_graphics_mod_manager->GetProjectionActions(xfmem.projection.type)) - { - projection_actions.push_back(action); - } - - for (const auto& texture : textures) - { - for (const auto& action : - g_graphics_mod_manager->GetProjectionTextureActions(xfmem.projection.type, texture)) - { - projection_actions.push_back(action); - } - } - } - - if (xf_state_manager.DidProjectionChange() || g_freelook_camera.GetController()->IsDirty() || - !projection_actions.empty() || m_projection_graphics_mod_change) + if (xf_state_manager.DidProjectionChange() || g_freelook_camera.GetController()->IsDirty()) { xf_state_manager.ResetProjection(); - m_projection_graphics_mod_change = !projection_actions.empty(); - auto corrected_matrix = LoadProjectionMatrix(); - - GraphicsModActionData::Projection projection{&corrected_matrix}; - for (const auto& action : projection_actions) - { - action->OnProjection(&projection); - } + auto corrected_matrix = LoadProjectionMatrix() * m_last_camera_modifier; memcpy(constants.projection.data(), corrected_matrix.data.data(), 4 * sizeof(float4)); dirty = true; diff --git a/Source/Core/VideoCommon/VertexShaderManager.h b/Source/Core/VideoCommon/VertexShaderManager.h index 9678aae58f..e29c2b6267 100644 --- a/Source/Core/VideoCommon/VertexShaderManager.h +++ b/Source/Core/VideoCommon/VertexShaderManager.h @@ -5,7 +5,6 @@ #include #include -#include #include "Common/BitSet.h" #include "Common/CommonTypes.h" @@ -26,7 +25,9 @@ public: // constant management void SetProjectionMatrix(XFStateManager& xf_state_manager); - void SetConstants(const std::vector& textures, XFStateManager& xf_state_manager); + void ForceProjectionMatrixUpdate(XFStateManager& xf_state_manager, + const Common::Matrix44& modifier); + void SetConstants(XFStateManager& xf_state_manager); // data: 3 floats representing the X, Y and Z vertex model coordinates and the posmatrix index. // out: 4 floats which will be initialized with the corresponding clip space coordinates @@ -82,7 +83,7 @@ private: alignas(16) std::array m_projection_matrix; // track changes - bool m_projection_graphics_mod_change = false; + Common::Matrix44 m_last_camera_modifier = Common::Matrix44::Identity(); Common::Matrix44 LoadProjectionMatrix(); }; diff --git a/Source/Core/VideoCommon/VideoBackendBase.cpp b/Source/Core/VideoCommon/VideoBackendBase.cpp index 8150120610..457009e4e8 100644 --- a/Source/Core/VideoCommon/VideoBackendBase.cpp +++ b/Source/Core/VideoCommon/VideoBackendBase.cpp @@ -51,6 +51,7 @@ #include "VideoCommon/FrameDumper.h" #include "VideoCommon/FramebufferManager.h" #include "VideoCommon/GeometryShaderManager.h" +#include "VideoCommon/GraphicsModSystem/Runtime/CustomResourceManager.h" #include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h" #include "VideoCommon/OnScreenDisplay.h" #include "VideoCommon/PixelEngine.h" @@ -343,6 +344,8 @@ bool VideoBackendBase::InitializeShared(std::unique_ptr gfx, memset(reinterpret_cast(&g_preprocess_cp_state), 0, sizeof(g_preprocess_cp_state)); s_tex_mem.fill(0); + auto& system = Core::System::GetInstance(); + // do not initialize again for the config window m_initialized = true; @@ -359,21 +362,19 @@ bool VideoBackendBase::InitializeShared(std::unique_ptr gfx, g_frame_dumper = std::make_unique(); g_framebuffer_manager = std::make_unique(); g_shader_cache = std::make_unique(); - g_graphics_mod_manager = std::make_unique(); g_widescreen = std::make_unique(); if (!g_vertex_manager->Initialize() || !g_shader_cache->Initialize() || !g_perf_query->Initialize() || !g_presenter->Initialize() || !g_framebuffer_manager->Initialize() || !g_texture_cache->Initialize() || (g_backend_info.bSupportsBBox && !g_bounding_box->Initialize()) || - !g_graphics_mod_manager->Initialize()) + !system.GetGraphicsModManager().Initialize()) { PanicAlertFmtT("Failed to initialize renderer classes"); Shutdown(); return false; } - auto& system = Core::System::GetInstance(); auto& command_processor = system.GetCommandProcessor(); command_processor.Init(); system.GetFifo().Init(); @@ -396,12 +397,16 @@ bool VideoBackendBase::InitializeShared(std::unique_ptr gfx, } g_shader_cache->InitializeShaderCache(); + system.GetCustomResourceManager().Initialize(); return true; } void VideoBackendBase::ShutdownShared() { + auto& system = Core::System::GetInstance(); + system.GetCustomResourceManager().Shutdown(); + g_frame_dumper.reset(); g_presenter.reset(); @@ -412,7 +417,6 @@ void VideoBackendBase::ShutdownShared() g_bounding_box.reset(); g_perf_query.reset(); - g_graphics_mod_manager.reset(); g_texture_cache.reset(); g_framebuffer_manager.reset(); g_shader_cache.reset(); @@ -424,7 +428,6 @@ void VideoBackendBase::ShutdownShared() m_initialized = false; - auto& system = Core::System::GetInstance(); VertexLoaderManager::Clear(); system.GetFifo().Shutdown(); } diff --git a/Source/Core/VideoCommon/VideoConfig.cpp b/Source/Core/VideoCommon/VideoConfig.cpp index 722bf7cedb..33d1333a3a 100644 --- a/Source/Core/VideoCommon/VideoConfig.cpp +++ b/Source/Core/VideoCommon/VideoConfig.cpp @@ -23,6 +23,7 @@ #include "VideoCommon/Fifo.h" #include "VideoCommon/FramebufferManager.h" #include "VideoCommon/FreeLookCamera.h" +#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModBackend.h" #include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h" #include "VideoCommon/OnScreenDisplay.h" #include "VideoCommon/PixelShaderManager.h" @@ -297,14 +298,16 @@ void CheckForConfigChanges() if (g_ActiveConfig.bGraphicMods && !old_graphics_mods_enabled) { - g_ActiveConfig.graphics_mod_config = GraphicsModGroupConfig(SConfig::GetInstance().GetGameID()); + g_ActiveConfig.graphics_mod_config = + GraphicsModSystem::Config::GraphicsModGroup(SConfig::GetInstance().GetGameID()); g_ActiveConfig.graphics_mod_config->Load(); } + auto& system = Core::System::GetInstance(); if (g_ActiveConfig.graphics_mod_config && (old_game_mod_changes != g_ActiveConfig.graphics_mod_config->GetChangeCount())) { - g_graphics_mod_manager->Load(*g_ActiveConfig.graphics_mod_config); + system.GetGraphicsModManager().Load(*g_ActiveConfig.graphics_mod_config); } // Update texture cache settings with any changed options. @@ -359,7 +362,6 @@ void CheckForConfigChanges() if (old_scale != g_framebuffer_manager->GetEFBScale()) { - auto& system = Core::System::GetInstance(); auto& pixel_shader_manager = system.GetPixelShaderManager(); pixel_shader_manager.Dirty(); } @@ -370,7 +372,7 @@ void CheckForConfigChanges() OSD::AddMessage("Video config changed, reloading shaders.", OSD::Duration::NORMAL); g_gfx->WaitForGPUIdle(); g_vertex_manager->InvalidatePipelineObject(); - g_vertex_manager->NotifyCustomShaderCacheOfHostChange(new_host_config); + system.GetGraphicsModManager().GetBackend().SetHostConfig(new_host_config); g_shader_cache->SetHostConfig(new_host_config); g_shader_cache->Reload(); g_framebuffer_manager->RecompileShaders(); diff --git a/Source/Core/VideoCommon/VideoConfig.h b/Source/Core/VideoCommon/VideoConfig.h index c51cefa784..6e5891c4cf 100644 --- a/Source/Core/VideoCommon/VideoConfig.h +++ b/Source/Core/VideoCommon/VideoConfig.h @@ -272,7 +272,7 @@ struct VideoConfig final bool bPreferVSForLinePointExpansion = false; int iBitrateKbps = 0; bool bGraphicMods = false; - std::optional graphics_mod_config; + std::optional graphics_mod_config; // Hacks bool bEFBAccessEnable = false; diff --git a/Source/Core/VideoCommon/XFMemory.h b/Source/Core/VideoCommon/XFMemory.h index e0a2696317..358c8dfb4f 100644 --- a/Source/Core/VideoCommon/XFMemory.h +++ b/Source/Core/VideoCommon/XFMemory.h @@ -68,7 +68,8 @@ enum class TexGenType : u32 Regular = 0, EmbossMap = 1, // Used when bump mapping Color0 = 2, - Color1 = 3 + Color1 = 3, + Passthrough = 4 // Used for custom meshes }; template <> struct fmt::formatter : EnumFormatter diff --git a/Source/VSProps/Base.Dolphin.props b/Source/VSProps/Base.Dolphin.props index eb1d50a141..48fae8b9fc 100644 --- a/Source/VSProps/Base.Dolphin.props +++ b/Source/VSProps/Base.Dolphin.props @@ -16,6 +16,7 @@ $(ExternalsDir)rangeset\include;%(AdditionalIncludeDirectories) $(ExternalsDir)Vulkan-Headers\include;%(AdditionalIncludeDirectories) $(ExternalsDir)VulkanMemoryAllocator\include;%(AdditionalIncludeDirectories) + $(ExternalsDir)watcher\watcher\include;%(AdditionalIncludeDirectories) $(ExternalsDir)WIL\include;%(AdditionalIncludeDirectories) WIL_SUPPRESS_EXCEPTIONS;%(PreprocessorDefinitions)