├── Citra AddOn ├── citra.addon ├── citra32.addon ├── README.md ├── Citra.fx └── citra.cpp ├── .github └── FUNDING.yml ├── README.md ├── mpv └── looking-glass-mpv.glsl └── interlaced-shader ├── lookingglass.glsl └── README.md /Citra AddOn/citra.addon: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakedowns/reshade-shaders/HEAD/Citra AddOn/citra.addon -------------------------------------------------------------------------------- /Citra AddOn/citra32.addon: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakedowns/reshade-shaders/HEAD/Citra AddOn/citra32.addon -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: jakedowns 4 | patreon: jakedowns 5 | ko_fi: jakedowns 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Citra Looking Glass Portrait Support / Citra ReShade Support 2 | 3 | ![image](https://user-images.githubusercontent.com/1683122/194764424-8130ca34-6f4b-40c7-a415-024b94a9affc.png) 4 | 5 | ### Method 1. Citra Addon + Citra Effect for ReShade: 6 | https://github.com/jakedowns/reshade-shaders/tree/main/Citra%20AddOn 7 | 8 | - more... fluid looking, since you're actually rendering more views 9 | - doesn't cover all depth effects, some UI/Sprites/Transitions etc render oddly 10 | - minor glitches (still a work in progress) 11 | 12 | ### Method 2. Looking Glass Portrait Interlaced Shader for Citra (no ReShade required) 13 | https://github.com/jakedowns/reshade-shaders/tree/main/interlaced-shader 14 | 15 | - better visual fidelity due to it simply displaying Left/Right images exactly as-rendered by 3DS emulator 16 | - more eye strain since it's only 2 views and stereo views on a display that supports 100 is kind of silly (you kind of have to hold your head in a "Sweet-spot" but, it does look good when you're in the sweet-spot) 17 | - option to have 1 sweet-spot (low cross talk, less forgiving for your neck) or repeating sweet-spots (more cross talk, more viable viewing angles) 18 | -------------------------------------------------------------------------------- /mpv/looking-glass-mpv.glsl: -------------------------------------------------------------------------------- 1 | //!HOOK OUTPUT 2 | //!BIND HOOKED 3 | //!DESC LookingGlass 4 | // ! WIDTH 1536 5 | // ! HEIGHT 2048 6 | 7 | // this is especially made for looking glass portrait. 8 | // TODO: make it more generic / able to support other variations 9 | const float width = 1536.0f; 10 | const float height = 2048.0f; 11 | const float dpi = 324.0f; 12 | 13 | 14 | 15 | // Calibration values 16 | // via: https://jakedowns.github.io/looking-glass-calibration.html 17 | const float slope = -7.083540916442871; 18 | const float center = 0.8167931437492371; 19 | const float pitch = 52.59267044067383; 20 | const float tilt = height / (width * slope); 21 | 22 | const float pitch_adjusted = pitch * width / dpi * cos(atan(1.0f, slope)); 23 | 24 | const float subp = 1.0f / (3.0f * width) * pitch_adjusted; 25 | const float repeat = 100.0f/3.0f; 26 | 27 | vec4 my_sample(float alpha){ 28 | const vec2 pos = vec2(HOOKED_pos); 29 | const float halfX = pos.x / 2.0f; 30 | 31 | // return HOOKED_texOff(vec2(-alpha,0.0)); 32 | 33 | if(fract(alpha) < .5){ 34 | // if(mod(fract(alpha)*100.0f,repeat) < repeat*0.5){ 35 | // if(pos.x < 0.5){ 36 | // right eye (right half of SBS); 37 | // return vec4(0.0,0.0,1.0,1.0); 38 | return HOOKED_tex(vec2(0.5 + halfX, pos.y)); 39 | } 40 | // left eye (left half of SBS) 41 | // return vec4(0.0); 42 | // return vec4(0.0,0.0,1.0,1.0); 43 | return HOOKED_tex(vec2(halfX, pos.y)); 44 | } 45 | 46 | vec4 hook(){ 47 | 48 | vec4 myColor = vec4(0.0,0.0,0.0,0.1);//HOOKED_tex(HOOKED_pos); 49 | 50 | // float alpha = (HOOKED_pos.x + (1.0-HOOKED_pos.y) * tilt) * pitch_adjusted - center; 51 | float alpha = (HOOKED_pos.x + (1.0-HOOKED_pos.y) * slope) * pitch_adjusted - center; 52 | 53 | // This makes a perfect red/cyan filter somehow 54 | // float alpha = gl_FragCoord.x; // + gl_FragCoord.y; 55 | 56 | // we sample 3 times since the r,g,b subpixels for each "original" pixel needs to be additionally shifted by one extra "subpixel" amount per channel to match the unique sub-pixel layout of the LKGP display 57 | // myColor = my_sample(alpha); 58 | myColor.r = my_sample(alpha).r; 59 | myColor.g = my_sample(alpha + subp).g; 60 | myColor.b = my_sample(alpha + 2.0f * subp).b; 61 | 62 | return myColor; 63 | } 64 | -------------------------------------------------------------------------------- /interlaced-shader/lookingglass.glsl: -------------------------------------------------------------------------------- 1 | // Citra 3DS Looking Glass Portrait Interlacing Shader 2 | // September 24, 2022 3 | // Created by Holophone3D and Jake Downs 4 | // inspired by Ian Reese & Yann Vernier (lonetech) 5 | // original source https://github.com/jakedowns/reshade-shaders/tree/main/interlaced-shader/ 6 | 7 | // Calibration values 8 | // via: https://jakedowns.github.io/looking-glass-calibration.html 9 | float slope = -7.083540916442871; 10 | float center = 0.8167931437492371; 11 | float pitch = 52.59267044067383; 12 | 13 | // this is especially made for looking glass portrait. 14 | // TODO: make it more generic / able to support other variations 15 | float width = o_resolution.y; 16 | float height = o_resolution.x; 17 | float dpi = 324.0f; 18 | 19 | float tilt = height / (width * slope); 20 | float pitch_adjusted = pitch * width / dpi * cos(atan(1.0, slope)); 21 | float subp = 1.0 / (3.0f * width) * pitch_adjusted; 22 | float repeat = 100/2; 23 | 24 | vec4 my_sample(float alpha){ 25 | // sample right eye 26 | 27 | // one-shot mode 28 | if(fract(alpha) > 0.145){ 29 | // if(alpha < -100){ 30 | // repeated mode 31 | //if(mod(fract(alpha)*100,repeat) >= repeat*0.5){ 32 | // return vec4(1,0,0,1); // debug color 33 | return texture(color_texture_r, frag_tex_coord); 34 | } 35 | // sample left eye 36 | // return vec4(0,0,1,1); // debug color 37 | return texture(color_texture, frag_tex_coord); 38 | } 39 | 40 | void main() { 41 | // if(screen == 1){ 42 | // // bottom screen 43 | // // straight texture read, nothing special to do 44 | // color = texture(color_texture, frag_tex_coord); 45 | // }else{ 46 | // top screen (3d interlaced for lenticular display) 47 | 48 | // alpha is... the offset for the subpixels for the current uv coordinate in the final interlaced texture, 49 | // which we assume is being drawn full screen, so 1536px wide 50 | 51 | // x and y are intentionally swapped here cause, 3DS LCDs are rotated, and the emulator maintains that 52 | // generate using our normalized uv 53 | float alpha = ( frag_tex_coord.y + frag_tex_coord.x * tilt ) * pitch_adjusted - center; 54 | 55 | // we sample 3 times since the r,g,b subpixels for each "original" pixel needs to be additionally shifted by one extra "subpixel" amount per channel to match the unique sub-pixel layout of the LKGP display 56 | color.r = my_sample(alpha).r; 57 | color.g = my_sample(alpha + subp).g; 58 | color.b = my_sample(alpha + 2.0f * subp).b; 59 | // } 60 | } 61 | -------------------------------------------------------------------------------- /interlaced-shader/README.md: -------------------------------------------------------------------------------- 1 | ## Looking Glass Portrait interlaced shader for Citra 3DS Emulator 2 | 3 | ### Basic Setup 4 | 5 | 1. In Citra; File > Open Citra Folder... 6 | 7 | 2. Go to Shaders directory and right-click to create a new text file called `lookingglass.glsl` 8 | 9 | 3. copy the contents of [lookingglass.glsl](./lookingglass.glsl) from this repo 10 | 11 | 4. Next, you need your calibration info. 12 | 13 | > Each LKGP is uniquely calibrated, and those values need to be specified in the shader to get the correct output. In future versions, I might automate this step. 14 | > Go to this URL and copy your Looking Glass Portrait's Calibration Data: https://jakedowns.github.io/looking-glass-calibration.html 15 | > NOTE: you can manually grab the values if you connect to your LKGP via USB and open it's internal storage in the explorer and navigate to `LKG_calibration/visual.json` 16 | 17 | 5. in `lookingglass.glsl` replace my calibration values with yours 18 | 19 | > **Note** you'll need at least Citra Nightly 1788 (2022-09-25) for this interlaced shader to load correctly. 20 | > If you're using a mainline or nightly older than 2022-09-25, you'll need to perform the following extra step to allow the shader to load: 21 | > 22 | > copy your shader to /anaglyph/lookingglass.glsl subfolder within the shaders folder 23 | > there's a bug where the shader file needs to be in two places to load properly citra-emu/citra#6133 24 | 25 | 6. Go to Emulation > Configure > Graphics 26 | 27 | 7. Set Stereoscopy > 3D Mode to Interlaced 28 | 29 | 8. Select Post-Processing Filter: lookingglass 30 | 31 | 9. enjoy! Let me know how it goes! https://twitter.com/jakedowns/status/1573681327005573120 32 | 33 | ### Advanced 34 | 35 | if you want to try dialing in the sweet spot yourself, 36 | you can try altering the shader, you can press F9 or F10 or F11 in citra to reload the shader without restarting. 37 | 38 | if it crashes, you've introduced a syntax error into the shader. undo and try again. 39 | 40 | The main values to be tweaked are: 41 | `float repeat = 100/3;` 42 | > // the number of times the LL/RR stereo pair will be repeated across the full viewing angle. 43 | > More repeats (100/4, 100/5) = more cross-talk, but more flexibility in viewing angles. 44 | > Lower divisions (100/1, 100/2) = less cross-talk, but there's only 1 or two "sweet spots" 45 | 46 | `< repeat*0.5` 47 | you can change this `0.5` to kind of... shift or rotate the sweet spot left or right 48 | 49 | ### Misc. 50 | 51 | If the looking-glass-calibration url above doesn't work, here's a fallback version on codesandbox: 52 | 53 | https://codesandbox.io/s/floral-flower-w2sjmx?file=/index.html 54 | 55 | If that doesn't work, try restarting Holoplay Service 56 | 57 | ### Related 58 | 59 | If you want to try something a little more advanced, check out my [Citra addon for ReShade, ReGlass & Refract](https://github.com/jakedowns/reshade-shaders/tree/main/Citra%20AddOn) 60 | -------------------------------------------------------------------------------- /Citra AddOn/README.md: -------------------------------------------------------------------------------- 1 | ## Citra Add-On + Citra.fx Effect for ReShade 2 | 3 | ### 🚧 \*\* **Work in Progress** \*\* 4 | 5 | **Easier Alternative for Beginners** if ReShade is a little too advanced for you, try starting with the shader-only setup. No ReShade needed, just Citra + a custom [Looking Glass Interlacing Shader for Citra](https://github.com/jakedowns/reshade-shaders/tree/main/interlaced-shader) 6 | 7 | ### Features: 8 | - normalizes Citra's depth buffers to be usable by other existing ReShade effects 9 | - rotates depth buffer so x,y uv coordinates are correct 10 | - inverts depth values so bright values are near and dark values are far 11 | - supports setting a fixed depth for the bottom screen when in split-screen mode 12 | 13 | ### How: 14 | - basically a clone of the Generic Depth addon, that remaps all calls from other effects/addons looking to access the `DEPTH` texture to instead access a pre-processed depth texture, specially modified to normalize it from Citra's emulation-specific values to something more standardly consumable by other shader effects pipelines 15 | 16 | ### Installation: 17 | 18 | > **Note** This addon requires you to install the version of reShade with full add on support 19 | ![image](https://user-images.githubusercontent.com/1683122/193439578-164b9797-dd68-4353-a3ae-5b9b48d564ee.png) 20 | 21 | > **Note** in order to test this, you'll need a custom build of Citra. You can build it yourself from this [tagged commit](https://github.com/jakedowns/citra-fix-custom-interlaced-shader-path/tree/reshade-left-eye-optional), or [download the citra-qt.exe from the release page here](https://github.com/jakedowns/citra-fix-custom-interlaced-shader-path/releases/tag/reshade-left-eye-optional) 22 | > 23 | > Hopefully the pull request to fix this gets merged: https://github.com/citra-emu/citra/pull/6140 24 | > or, i'll find another way to capture the left-eye's depth buffer to negate the need for this patch 25 | 26 | 27 | 1. download [`citra.addon`](./citra.addon) (64-bit) OR [`citra32.addon`](./citra32.addon) (32-bit) into the same directory as `ReShade.ini` and `citra-qt.exe` 28 | 2. place `Citra.fx` in `./reshade-shaders/Shaders/` sub-directory within the Citra executable folder 29 | 4. start citra. (if it crashes or reshade doesn't launch, disable generic depth, edit ReShade.ini: 30 | ``` 31 | [ADDON] 32 | DisabledAddons=Generic Depth 33 | ``` 34 | 5. start citra. press `home` to bring up ReShade **Important** Disable the `Generic Depth` add-on 35 | 6. make sure `Citra` is enabled in the add-ons tab 36 | 7. make sure the `Citra` effect is enabled in the home tab, and that it's at the top of the effects list 37 | 38 | 39 | 40 | 41 | ### Recommended / Tested Effects: 42 | 43 | - *Looking Glass Portrait Support:* 44 | - [ReGlass](https://github.com/jbienz/ReGlass) aka [LookingGlass.fx](https://github.com/jbienz/ReGlass/blob/main/Shaders/LookingGlass.fx) 45 | - with [Refract](https://github.com/SolerSoft/Refract) 46 | 47 | - [MXAO.fx](https://github.com/cyrie/Stormshade/blob/master/reshade-shaders/Shaders/MXAO.fx) 48 | - [CinematicDOF.fx](https://github.com/FransBouma/OtisFX/blob/master/Shaders/CinematicDOF.fx) 49 | - mcflypg / Pascal Gilcher's ReShade Ray Tracing shader (RTGI) - https://www.patreon.com/mcflypg 50 | 51 | ### Known Issues 52 | 53 | - currently, if you have too many, or too intense fx enabled, or resolution too high, you might see flickering. 54 | 55 | this is due to the fact that many games share a single depth buffer. i'm still working on a solution to prevent this. 56 | 57 | - currently the "left, right, small" side-by-side modes are unfinished and require manual offset adjustments 58 | 59 | - scaling adjustments and aspect adjustments need re-enabled 60 | 61 | - certain window dimensions require a lot of manual offset adjustments, i'm working on automating these 62 | 63 | ### Credits 64 | - [crosire](https://github.com/crosire/reshade) - Big thanks for giving us the guidance to get this add-on working 65 | - [Edgarska](https://www.reddit.com/r/Citra/comments/i4o5i1/reshade_depth_buffer_access_fix/) - their reddit post in /r/ReShade, [Reshade depth buffer access fix.](https://www.reddit.com/r/Citra/comments/i4o5i1/reshade_depth_buffer_access_fix/) gave us a lot of the basics we needed to get this working. I simply packaged them up. 66 | - Holophone3D - thanks for the motivation and inspiration to get this working. also thanks for getting the ball rolling with the basics for the shader / fx code. 67 | - [jbienz](https://github.com/jbienz) / [SolerSoft](https://github.com/SolerSoft) - thanks for your enthusiasm and encouragement, and for your great work on ReGlass & Refract 68 | - emufan4568 - thanks for your help on the Citra discord #development channel 69 | - jakedowns - follow me on twitter.com/jakedowns for future updates | support my dev work on patreon.com/jakedownsthings | follow my art work on instagram.com/jakedownsthings 70 | 71 | ### Video 72 | 73 | 74 | 75 | https://user-images.githubusercontent.com/1683122/196085074-107558b8-6426-4692-a38a-8b72487b69ec.mp4 76 | 77 | 78 | 79 | ### Images 80 | ![image](https://user-images.githubusercontent.com/1683122/193262894-73fb5d86-0a54-4ef2-bf0d-61bba3a800a5.png) 81 | ![](https://i.imgur.com/6uZalSm.png) 82 | ![](https://i.imgur.com/AqOIa53.png) 83 | 84 | -------------------------------------------------------------------------------- /Citra AddOn/Citra.fx: -------------------------------------------------------------------------------- 1 | /* 2 | Citra by Holophone3D, Jake Downs, Jared Bienz. 3 | Recommended to be used with: 4 | https://github.com/jbienz/ReGlass & https://github.com/SolerSoft/Refract 5 | 6 | Preprocess depth map textures from Citra so they can be used by other add-ons 7 | - pre-swap (un-rotate) depth buffer xy coordinates 8 | 9 | */ 10 | 11 | #include "ReShade.fxh" 12 | 13 | // -- Options -- 14 | 15 | uniform float BUFFER_AR = BUFFER_WIDTH / BUFFER_HEIGHT; 16 | 17 | uniform int iUIBottomScreenPosition < 18 | ui_type = "combo"; 19 | ui_label = "Bottom Screen Position"; 20 | ui_category = "Bottom Screen"; 21 | ui_items = "Bottom\0" 22 | "Top\0" 23 | "Left\0" 24 | "Right\0" 25 | "Disabled\0"; 26 | > = 0; 27 | 28 | uniform float fUIBottomFocus < 29 | ui_type = "drag"; 30 | ui_label = "Bottom Screen Focus"; 31 | ui_category = "Bottom Screen"; 32 | ui_tooltip = "Adjust bottom screen near/far focus.\n"; 33 | ui_min = 0.0; ui_max = 1.0; 34 | ui_step = 0.05; 35 | > = 0.5; 36 | 37 | uniform bool bUIPreviewDepth < 38 | ui_category = "Preview Depth Buffer"; 39 | ui_label = "Preview Depth Buffer"; 40 | > = false; 41 | 42 | uniform float bUIPreviewAlpha < 43 | ui_type = "drag"; 44 | ui_category = "Preview Depth Buffer"; 45 | ui_label = "Preview Alpha"; 46 | ui_min = 0.0; ui_max = 1.0; 47 | > = 1.0; 48 | 49 | uniform float fUINearPlane < 50 | ui_type = "drag"; 51 | ui_label = "Near Plane"; 52 | ui_category = "Depth"; 53 | ui_tooltip = "RESHADE_DEPTH_LINEARIZATION_FAR_PLANE="; 54 | ui_min = 0.0; ui_max = 1000.0; 55 | ui_step = 0.01; 56 | > = 0.0; 57 | 58 | uniform float fUIFarPlane < 59 | ui_type = "drag"; 60 | ui_label = "Far Plane"; 61 | ui_category = "Depth"; 62 | ui_tooltip = "RESHADE_DEPTH_LINEARIZATION_FAR_PLANE="; 63 | ui_min = 0.0; ui_max = 1000.0; 64 | ui_step = 0.001; 65 | > = 0.01; 66 | 67 | uniform float fUIDepthMultiplier < 68 | ui_type = "drag"; 69 | ui_label = "Multiplier"; 70 | ui_category = "Depth"; 71 | ui_tooltip = "RESHADE_DEPTH_MULTIPLIER="; 72 | ui_min = 0.0; ui_max = 1000.0; 73 | ui_step = 0.001; 74 | > = 1.0; 75 | 76 | // -- Aspect Options -- 77 | 78 | uniform bool bUIUseCustomAspectRatio < 79 | ui_label = "Use Custom Aspect Ratio"; 80 | ui_category = "Aspect ratio"; 81 | > = false; 82 | 83 | uniform float AspectRatio < 84 | ui_type = "drag"; 85 | ui_label = "Correct proportions"; 86 | ui_category = "Aspect ratio"; 87 | ui_min = -4.0; ui_max = 4.0; 88 | ui_step = 0.01; 89 | > = 1.0; 90 | 91 | uniform float ScaleX < 92 | ui_type = "drag"; 93 | ui_label = "Scale image X"; 94 | ui_category = "Aspect ratio"; 95 | ui_min = 0.0; ui_max = 4.0; 96 | ui_step = 0.001; 97 | > = 1.0; 98 | 99 | uniform float ScaleY < 100 | ui_type = "drag"; 101 | ui_label = "Scale image Y"; 102 | ui_category = "Aspect ratio"; 103 | ui_min = 0.0; ui_max = 4.0; 104 | ui_step = 0.001; 105 | > = 1.0; 106 | 107 | uniform float fUIDepthXOffset < 108 | ui_label = "Offset X Relative"; 109 | ui_category = "Offsets"; 110 | ui_type = "drag"; 111 | ui_step = 0.001; 112 | > = 0.0; 113 | 114 | // uniform float fUIDepthXPxOffset < 115 | // ui_label = "Offset X in pixels"; 116 | // ui_category = "Offsets"; 117 | // ui_type = "drag"; 118 | // > = 0.0; 119 | 120 | uniform float fUIDepthYOffset < 121 | ui_label = "Offset Y Relative"; 122 | ui_category = "Offsets"; 123 | ui_type = "drag"; 124 | ui_step = 0.001; 125 | > = 0.0; 126 | 127 | // uniform float fUIDepthYPxOffset < 128 | // ui_label = "Offset Y in pixels"; 129 | // ui_category = "Offsets"; 130 | // ui_type = "drag"; 131 | // > = 0.0; 132 | 133 | uniform bool FitScreen < 134 | ui_label = "Scale image to borders"; 135 | ui_category = "Aspect ratio"; 136 | > = true; 137 | 138 | uniform float4 Color < 139 | ui_label = "Background color"; 140 | ui_category = "Aspect ratio"; 141 | ui_type = "color"; 142 | > = float4(0.027, 0.027, 0.027, 0.17); 143 | 144 | // uniform int iUIPresentType < 145 | // ui_type = "combo"; 146 | // ui_label = "Present type"; 147 | // ui_items = "Depth map\0" 148 | // "Normal map\0" 149 | // "Show both (Vertical 50/50)\0"; 150 | // > = 2; 151 | 152 | texture OrigDepthTex : ORIG_DEPTH; 153 | sampler OrigDepth{ Texture = OrigDepthTex; }; 154 | 155 | texture ModifiedDepthTex{ Width = BUFFER_WIDTH; Height = BUFFER_HEIGHT; Format = R32F; }; 156 | 157 | // float3 AspectRatioPS( 158 | // float4 pos : SV_Position, 159 | // float2 texcoord : TEXCOORD, 160 | // float aspect, 161 | // float zoom, 162 | // bool fitscreen, 163 | // int depth 164 | // ) : SV_Target 165 | // { 166 | // bool mask = false; 167 | 168 | // // Center coordinates 169 | // float2 coord = texcoord-0.5; 170 | 171 | // // if (Zoom != 1.0) coord /= Zoom; 172 | // if (zoom != 1.0) coord /= clamp(zoom, 1.0, 1.5); // Anti-cheat 173 | 174 | // // Squeeze horizontally 175 | // if (aspect<0) 176 | // { 177 | // coord.x *= abs(aspect)+1.0; // Apply distortion 178 | 179 | // // Scale to borders 180 | // if (fitscreen) coord /= abs(aspect)+1.0; 181 | // else // mask image borders 182 | // mask = abs(coord.x)>0.5; 183 | // } 184 | // // Squeeze vertically 185 | // else if (aspect>0) 186 | // { 187 | // coord.y *= aspect+1.0; // Apply distortion 188 | 189 | // // Scale to borders 190 | // if (fitscreen) coord /= abs(aspect)+1.0; 191 | // else // mask image borders 192 | // mask = abs(coord.y)>0.5; 193 | // } 194 | 195 | // // Coordinates back to the corner 196 | // coord += 0.5; 197 | 198 | // // Sample display image and return 199 | // if(mask){ 200 | // return Color.rgb; 201 | // } 202 | 203 | // return depth ? tex2D(OrigDepth, coord).rgb : tex2D(ReShade::BackBuffer, coord).rgb; 204 | // } 205 | 206 | bool isBottomScreenPx(float2 input_tex) { 207 | 208 | // disabled? return false 209 | if (iUIBottomScreenPosition == 4) { 210 | return false; 211 | } 212 | 213 | bool isBottomScreenPixel = false; 214 | if (iUIBottomScreenPosition == 0) { 215 | // bottom 216 | isBottomScreenPixel = input_tex.x > 0.5; 217 | } else if (iUIBottomScreenPosition == 1) { 218 | // top 219 | isBottomScreenPixel = input_tex.x < 0.5; 220 | } else if (iUIBottomScreenPosition == 2) { 221 | // left 222 | isBottomScreenPixel = input_tex.y > 0.55546875; 223 | } else if (iUIBottomScreenPosition == 3) { 224 | // right 225 | isBottomScreenPixel = input_tex.y < 1.0 - 0.55546875; 226 | } 227 | // else if (iUIBottomScreenPosition == 4) { 228 | // // right > small 229 | // isBottomScreenPixel = input_tex.y > 0.83333333; 230 | // } 231 | 232 | // return false; 233 | return isBottomScreenPixel; 234 | } 235 | 236 | float2 scaleCoordinates(float2 mytexcoord){ 237 | 238 | int2 depthSize = tex2Dsize(OrigDepth).yx; 239 | int2 bufferSize = int2(BUFFER_WIDTH, BUFFER_HEIGHT); 240 | 241 | float scaled_width, scaled_height, max_scaled_width, max_scaled_height; 242 | if( 243 | iUIBottomScreenPosition == 0 244 | || iUIBottomScreenPosition == 1 245 | ){ 246 | // over/under top screen scaled relative dimensions 247 | // 400 * 240 (really 400 x 480) 248 | // 400 scaled width 249 | // --- x --- 250 | // 240 scaled height (BUFFER_HEIGHT/2) 251 | scaled_width = BUFFER_WIDTH < BUFFER_HEIGHT ? BUFFER_WIDTH : (BUFFER_HEIGHT/2) * 400 / 240; 252 | scaled_height = BUFFER_HEIGHT <= BUFFER_WIDTH ? BUFFER_HEIGHT/2 : BUFFER_WIDTH * 240 / 400; 253 | } 254 | else if( 255 | iUIBottomScreenPosition == 2 256 | || iUIBottomScreenPosition == 3 257 | ){ 258 | // sbs 400+320 by 240 259 | // 720 scaled width 260 | // --- x --- 261 | // 240 scaled height 262 | max_scaled_height = BUFFER_WIDTH * 240 / 720; 263 | scaled_height = BUFFER_HEIGHT < max_scaled_height ? BUFFER_HEIGHT : max_scaled_height; 264 | max_scaled_width = BUFFER_HEIGHT < max_scaled_height ? BUFFER_HEIGHT * 720 / 240 : BUFFER_WIDTH; 265 | scaled_width = max_scaled_width * 0.5556; // 400 / 720 266 | } 267 | else if( 268 | iUIBottomScreenPosition == 4 269 | ){ 270 | max_scaled_width = BUFFER_HEIGHT * 400 / 240; 271 | scaled_width = BUFFER_WIDTH > max_scaled_width ? max_scaled_width : BUFFER_WIDTH; 272 | 273 | max_scaled_height = BUFFER_WIDTH * 240 / 400; 274 | scaled_height = BUFFER_HEIGHT > max_scaled_height ? max_scaled_height : BUFFER_HEIGHT; 275 | } 276 | 277 | // map FULL BUFFER coordinate system down to just where the top screen is, relatively within the buffer 278 | // so that when the depth map is sampled it's contents align to where the rgb top screen is rendered within the output buffer 279 | 280 | if(iUIBottomScreenPosition == 0){ 281 | // top screen is on top, bottom screen is on bottom 282 | 283 | // horizontal is still controlled by "y" here even tho i created this coord as .yx 284 | mytexcoord.y -= .5; 285 | mytexcoord.y /= scaled_width / BUFFER_WIDTH; 286 | mytexcoord.y += .5; 287 | 288 | // vert 289 | mytexcoord.x -= 0.5; 290 | mytexcoord.x /= scaled_height / BUFFER_HEIGHT; 291 | } 292 | else if(iUIBottomScreenPosition == 1){ 293 | // top screen is below bottom screen 294 | 295 | // horizontal is still controlled by "y" here even tho i created this coord as .yx 296 | mytexcoord.y -= .5; 297 | mytexcoord.y /= scaled_width / BUFFER_WIDTH; 298 | mytexcoord.y += .5; 299 | 300 | // vert 301 | mytexcoord.x -= 0.5; 302 | mytexcoord.x /= scaled_height / BUFFER_HEIGHT; 303 | mytexcoord.x += 1.0; 304 | 305 | } 306 | else if(iUIBottomScreenPosition == 2){ 307 | // top screen is left of bottom screen 308 | mytexcoord.y -= .5; 309 | mytexcoord.y /= scaled_width / BUFFER_WIDTH; 310 | mytexcoord.y += .1; 311 | 312 | mytexcoord.x -= .5; 313 | mytexcoord.x /= max_scaled_height / BUFFER_HEIGHT; 314 | mytexcoord.x += .5; 315 | } 316 | else if(iUIBottomScreenPosition == 3){ 317 | // top screen is right of bottom screen 318 | 319 | mytexcoord.y -= .5; 320 | mytexcoord.y /= scaled_width / BUFFER_WIDTH; 321 | mytexcoord.y += .9; 322 | 323 | mytexcoord.x -= .5; 324 | mytexcoord.x /= max_scaled_height / BUFFER_HEIGHT; 325 | mytexcoord.x += .5; 326 | } 327 | // else if(iUIBottomScreenPosition == 4){ 328 | // // // right small 329 | // // texcoord.y = texcoord.y * 1.22; 330 | // // texcoord.y = texcoord.y - (1.0 - 0.83333333); 331 | // } 332 | else { 333 | // fullscreen 334 | 335 | mytexcoord.y -= .5; 336 | mytexcoord.y /= scaled_width / BUFFER_WIDTH; 337 | mytexcoord.y += .5; 338 | 339 | 340 | mytexcoord.x -= .5; 341 | mytexcoord.x /= scaled_height / BUFFER_HEIGHT; 342 | mytexcoord.x += .5; 343 | 344 | } 345 | 346 | // global scaling 347 | // texcoord.x = texcoord.x / ScaleY; 348 | // texcoord.y = texcoord.y / ScaleX; 349 | 350 | // if(bUIUseCustomAspectRatio){ 351 | // texcoord.x = texcoord.x * AspectRatio; 352 | // texcoord.y = texcoord.y * AspectRatio; 353 | // } 354 | // else 355 | // texcoord.y = texcoord.y * BUFFER_AR; 356 | 357 | return mytexcoord; 358 | } 359 | 360 | float GetModDepth(float2 tex : TEXCOORD) { 361 | float2 input_tex = float2(tex.y, tex.x); 362 | float2 mytex = 1.0 - float2(tex.y, tex.x); 363 | 364 | if (isBottomScreenPx(input_tex)){ 365 | return fUIBottomFocus; 366 | } 367 | 368 | mytex = scaleCoordinates(mytex); 369 | 370 | // if(bUIDepthIsUpsideDown){ 371 | // tex.y = 1.0 - tex.y; 372 | // } 373 | 374 | // mytex.x /= fUIDepthXScale; 375 | // mytex.y /= fUIDepthYScale; 376 | 377 | // if(fUIDepthXOffset){ 378 | // mytex.y += fUIDepthXOffset / 2.000000001; 379 | // } 380 | // // else if(fUIDepthXPxOffset){ 381 | // // mytex.x -= fUIDepthXPxOffset * BUFFER_RCP_WIDTH; 382 | // // } 383 | 384 | // if(fUIDepthYOffset){ 385 | // mytex.x -= fUIDepthYOffset / 2.000000001; 386 | // } 387 | // // else if(fUIDepthYPxOffset){ 388 | // // mytex.y += fUIDepthYPxOffset * BUFFER_RCP_HEIGHT; 389 | // // } 390 | 391 | 392 | float depth = tex2Dlod(OrigDepth, float4(mytex, 0, 0)).x * fUIDepthMultiplier; 393 | 394 | // if(bUIDepthIsLog){ 395 | // const float C = 0.01; 396 | // depth = (exp(depth * log(C + 1.0)) - 1.0) / C; 397 | // } 398 | 399 | // invert by default for citra 400 | depth = 1.0 - depth; 401 | // if(!bUIDepthIsReversed){ 402 | // depth = 1.0 - depth; 403 | // } 404 | 405 | const float N = 1.0; 406 | depth /= fUIFarPlane - depth * (fUIFarPlane - N); 407 | 408 | return depth; 409 | } 410 | 411 | float4 MyPS(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET { 412 | float depth = GetModDepth(tex); 413 | return float4(depth.xxx,1.0); 414 | } 415 | 416 | float4 PreviewDepth(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET { 417 | if(bUIPreviewDepth){ 418 | float depth = GetModDepth(tex); 419 | return lerp(tex2D(ReShade::BackBuffer, tex), float4(depth.xxx,1.0), bUIPreviewAlpha); 420 | } 421 | return tex2D(ReShade::BackBuffer, tex); 422 | } 423 | 424 | 425 | // FullscreenVS 426 | technique Citra { 427 | pass { 428 | VertexShader = PostProcessVS; 429 | PixelShader = MyPS; 430 | RenderTarget = ModifiedDepthTex; 431 | } 432 | pass { 433 | VertexShader = PostProcessVS; 434 | PixelShader = PreviewDepth; 435 | } 436 | } 437 | -------------------------------------------------------------------------------- /Citra AddOn/citra.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * 2022 Jake Downs 3 | */ 4 | 5 | /* 6 | * 7 | * Based on generic_depth 8 | * Copyright (C) 2021 Patrick Mours 9 | * SPDX-License-Identifier: BSD-3-Clause 10 | */ 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | using namespace reshade::api; 22 | 23 | static std::shared_mutex s_mutex; 24 | 25 | static bool s_disable_intz = false; 26 | // Enable or disable the creation of backup copies at clear operations on the selected depth-stencil 27 | static unsigned int s_preserve_depth_buffers = 0; 28 | // Enable or disable the aspect ratio check from 'check_aspect_ratio' in the detection heuristic 29 | static unsigned int s_use_aspect_ratio_heuristics = 0; 30 | 31 | enum class clear_op 32 | { 33 | clear_depth_stencil_view, 34 | fullscreen_draw, 35 | unbind_depth_stencil_view, 36 | }; 37 | 38 | struct draw_stats 39 | { 40 | uint32_t vertices = 0; 41 | uint32_t drawcalls = 0; 42 | uint32_t drawcalls_indirect = 0; 43 | viewport last_viewport = {}; 44 | }; 45 | struct clear_stats : public draw_stats 46 | { 47 | clear_op clear_op = clear_op::clear_depth_stencil_view; 48 | bool copied_during_frame = false; 49 | }; 50 | 51 | struct depth_stencil_info 52 | { 53 | draw_stats total_stats; 54 | draw_stats current_stats; // Stats since last clear operation 55 | std::vector clears; 56 | bool copied_during_frame = false; 57 | }; 58 | 59 | struct depth_stencil_hash 60 | { 61 | inline size_t operator()(resource value) const 62 | { 63 | // Simply use the handle (which is usually a pointer) as hash value (with some bits shaved off due to pointer alignment) 64 | return static_cast(value.handle >> 4); 65 | } 66 | }; 67 | 68 | struct __declspec(uuid("ad059cc1-c3ad-4cef-a4a9-401f672c6c37")) state_tracking 69 | { 70 | viewport current_viewport = {}; 71 | resource current_depth_stencil = { 0 }; 72 | std::unordered_map counters_per_used_depth_stencil; 73 | bool first_draw_since_bind = true; 74 | draw_stats best_copy_stats; 75 | 76 | state_tracking() 77 | { 78 | // Reserve some space upfront to avoid rehashing during command recording 79 | counters_per_used_depth_stencil.reserve(32); 80 | } 81 | 82 | void reset() 83 | { 84 | reset_on_present(); 85 | current_depth_stencil = { 0 }; 86 | } 87 | void reset_on_present() 88 | { 89 | best_copy_stats = { 0, 0 }; 90 | counters_per_used_depth_stencil.clear(); 91 | } 92 | 93 | void merge(const state_tracking &source) 94 | { 95 | // Executing a command list in a different command list inherits state 96 | current_depth_stencil = source.current_depth_stencil; 97 | 98 | if (source.best_copy_stats.vertices >= best_copy_stats.vertices) 99 | best_copy_stats = source.best_copy_stats; 100 | 101 | if (source.counters_per_used_depth_stencil.empty()) 102 | return; 103 | 104 | counters_per_used_depth_stencil.reserve(source.counters_per_used_depth_stencil.size()); 105 | for (const auto &[depth_stencil_handle, snapshot] : source.counters_per_used_depth_stencil) 106 | { 107 | depth_stencil_info &target_snapshot = counters_per_used_depth_stencil[depth_stencil_handle]; 108 | target_snapshot.total_stats.vertices += snapshot.total_stats.vertices; 109 | target_snapshot.total_stats.drawcalls += snapshot.total_stats.drawcalls; 110 | target_snapshot.total_stats.drawcalls_indirect += snapshot.total_stats.drawcalls_indirect; 111 | target_snapshot.current_stats.vertices += snapshot.current_stats.vertices; 112 | target_snapshot.current_stats.drawcalls += snapshot.current_stats.drawcalls; 113 | target_snapshot.current_stats.drawcalls_indirect += snapshot.current_stats.drawcalls_indirect; 114 | 115 | target_snapshot.clears.insert(target_snapshot.clears.end(), snapshot.clears.begin(), snapshot.clears.end()); 116 | 117 | target_snapshot.copied_during_frame |= snapshot.copied_during_frame; 118 | } 119 | } 120 | }; 121 | 122 | struct __declspec(uuid("7c6363c7-f94e-437a-9160-141782c44a98")) generic_depth_data 123 | { 124 | // The depth-stencil resource that is currently selected as being the main depth target 125 | resource selected_depth_stencil = { 0 }; 126 | 127 | // Resource used to override automatic depth-stencil selection 128 | resource override_depth_stencil = { 0 }; 129 | 130 | // The current depth shader resource view bound to shaders 131 | // This can be created from either the selected depth-stencil resource (if it supports shader access) or from a backup resource 132 | resource_view selected_shader_resource = { 0 }; 133 | 134 | // True when the shader resource view was created from the backup resource, false when it was created from the original depth-stencil 135 | bool using_backup_texture = false; 136 | 137 | std::unordered_map display_count_per_depth_stencil; 138 | }; 139 | 140 | struct depth_stencil_backup 141 | { 142 | // The number of effect runtimes referencing this backup 143 | size_t references = 1; 144 | 145 | // A resource used as target for a backup copy of this depth-stencil 146 | resource backup_texture = { 0 }; 147 | 148 | // The depth-stencil that should be copied from 149 | resource depth_stencil_resource = { 0 }; 150 | 151 | // Set to zero for automatic detection, otherwise will use the clear operation at the specific index within a frame 152 | size_t force_clear_index = 0; 153 | 154 | // Frame dimensions of the last effect runtime this backup was used with 155 | uint32_t frame_width = 0; 156 | uint32_t frame_height = 0; 157 | }; 158 | 159 | struct __declspec(uuid("e006e162-33ac-4b9f-b10f-0e15335c7bdb")) generic_depth_device_data 160 | { 161 | // List of queues created for this device 162 | std::vector queues; 163 | 164 | // List of resources that were deleted this frame 165 | std::vector destroyed_resources; 166 | 167 | // List of resources that are enqueued for delayed destruction in the future 168 | std::vector> delayed_destroy_resources; 169 | 170 | // List of all encountered depth-stencils of the last frame 171 | std::vector> current_depth_stencil_list; 172 | 173 | // List of depth-stencils that should be tracked throughout each frame and potentially be backed up during clear operations 174 | std::vector depth_stencil_backups; 175 | 176 | depth_stencil_backup *find_depth_stencil_backup(resource resource) 177 | { 178 | for (depth_stencil_backup &backup : depth_stencil_backups) 179 | if (backup.depth_stencil_resource == resource) 180 | return &backup; 181 | return nullptr; 182 | } 183 | 184 | depth_stencil_backup *track_depth_stencil_for_backup(device *device, resource resource, resource_desc desc) 185 | { 186 | const auto it = std::find_if(depth_stencil_backups.begin(), depth_stencil_backups.end(), 187 | [resource](const depth_stencil_backup &existing) { return existing.depth_stencil_resource == resource; }); 188 | if (it != depth_stencil_backups.end()) 189 | { 190 | it->references++; 191 | return &(*it); 192 | } 193 | 194 | depth_stencil_backup &backup = depth_stencil_backups.emplace_back(); 195 | backup.depth_stencil_resource = resource; 196 | 197 | desc.type = resource_type::texture_2d; 198 | desc.heap = memory_heap::gpu_only; 199 | desc.usage = resource_usage::shader_resource | resource_usage::copy_dest; 200 | 201 | if (device->get_api() == device_api::d3d9) 202 | desc.texture.format = format::r32_float; // D3DFMT_R32F, since INTZ does not support D3DUSAGE_RENDERTARGET which is required for copying 203 | // Use depth format as-is in OpenGL and Vulkan, since those are valid for shader resource views there 204 | else if (device->get_api() != device_api::opengl && device->get_api() != device_api::vulkan) 205 | desc.texture.format = format_to_typeless(desc.texture.format); 206 | 207 | // First try to revive a backup resource that was previously enqueued for delayed destruction 208 | for (auto delayed_destroy_it = delayed_destroy_resources.begin(); delayed_destroy_it != delayed_destroy_resources.end(); ++delayed_destroy_it) 209 | { 210 | const resource_desc delayed_destroy_desc = device->get_resource_desc(delayed_destroy_it->first); 211 | 212 | if (desc.texture.width == delayed_destroy_desc.texture.width && desc.texture.height == delayed_destroy_desc.texture.height && desc.texture.format == delayed_destroy_desc.texture.format) 213 | { 214 | backup.backup_texture = delayed_destroy_it->first; 215 | delayed_destroy_resources.erase(delayed_destroy_it); 216 | return &backup; 217 | } 218 | } 219 | 220 | if (device->create_resource(desc, nullptr, resource_usage::copy_dest, &backup.backup_texture)) 221 | device->set_resource_name(backup.backup_texture, "ReShade depth backup texture"); 222 | else 223 | reshade::log_message(1, "Failed to create backup depth-stencil texture!"); 224 | 225 | return &backup; 226 | } 227 | 228 | void untrack_depth_stencil(resource resource) 229 | { 230 | const auto it = std::find_if(depth_stencil_backups.begin(), depth_stencil_backups.end(), 231 | [resource](const depth_stencil_backup &existing) { return existing.depth_stencil_resource == resource; }); 232 | if (it == depth_stencil_backups.end() || --it->references != 0) 233 | return; 234 | 235 | depth_stencil_backup &backup = *it; 236 | 237 | if (backup.backup_texture != 0) 238 | { 239 | // Do not destroy backup texture immediately since it may still be referenced by a command list that is in flight or was prerecorded 240 | // Instead enqueue it for delayed destruction in the future 241 | delayed_destroy_resources.emplace_back(backup.backup_texture, 50); // Destroy after 50 frames 242 | } 243 | 244 | depth_stencil_backups.erase(it); 245 | } 246 | }; 247 | 248 | // Checks whether the aspect ratio of the two sets of dimensions is similar or not 249 | static bool check_aspect_ratio(float width_to_check, float height_to_check, uint32_t width, uint32_t height) 250 | { 251 | if (width_to_check == 0.0f || height_to_check == 0.0f) 252 | return true; 253 | 254 | const float w = static_cast(width); 255 | float w_ratio = w / width_to_check; 256 | const float h = static_cast(height); 257 | float h_ratio = h / height_to_check; 258 | const float aspect_ratio = (w / h) - (static_cast(width_to_check) / height_to_check); 259 | 260 | // Accept if dimensions are similar in value or almost exact multiples 261 | return std::fabs(aspect_ratio) <= 0.1f && ((w_ratio <= 1.85f && w_ratio >= 0.5f && h_ratio <= 1.85f && h_ratio >= 0.5f) || (s_use_aspect_ratio_heuristics == 2 && std::modf(w_ratio, &w_ratio) <= 0.02f && std::modf(h_ratio, &h_ratio) <= 0.02f)); 262 | } 263 | 264 | static void on_clear_depth_impl(command_list *cmd_list, state_tracking &state, resource depth_stencil, clear_op op) 265 | { 266 | if (depth_stencil == 0) 267 | return; 268 | 269 | device *const device = cmd_list->get_device(); 270 | 271 | depth_stencil_backup *const depth_stencil_backup = device->get_private_data().find_depth_stencil_backup(depth_stencil); 272 | if (depth_stencil_backup == nullptr || depth_stencil_backup->backup_texture == 0) 273 | return; 274 | 275 | bool do_copy = true; 276 | depth_stencil_info &counters = state.counters_per_used_depth_stencil[depth_stencil]; 277 | 278 | // Ignore clears when there was no meaningful workload (e.g. at the start of a frame) 279 | if (counters.current_stats.drawcalls == 0) 280 | return; 281 | 282 | // Ignore clears when the last viewport rendered to only affected a small subset of the depth-stencil (fixes flickering in some games) 283 | switch (op) 284 | { 285 | case clear_op::clear_depth_stencil_view: 286 | // Mirror's Edge and Portal occasionally render something into a small viewport (16x16 in Mirror's Edge, 512x512 in Portal to render underwater geometry) 287 | do_copy = counters.current_stats.last_viewport.width > 1024 || (counters.current_stats.last_viewport.width == 0 || depth_stencil_backup->frame_width <= 1024); 288 | break; 289 | case clear_op::fullscreen_draw: 290 | // Mass Effect 3 in Mass Effect Legendary Edition sometimes uses a larger common depth buffer for shadow map and scene rendering, where the former uses a 1024x1024 viewport and the latter uses a viewport matching the render resolution 291 | do_copy = check_aspect_ratio(counters.current_stats.last_viewport.width, counters.current_stats.last_viewport.height, depth_stencil_backup->frame_width, depth_stencil_backup->frame_height); 292 | break; 293 | case clear_op::unbind_depth_stencil_view: 294 | break; 295 | } 296 | 297 | if (do_copy) 298 | { 299 | if (op != clear_op::unbind_depth_stencil_view) 300 | { 301 | // If clear index override is set to zero, always copy any suitable buffers 302 | if (depth_stencil_backup->force_clear_index == 0) 303 | { 304 | // Use greater equals operator here to handle case where the same scene is first rendered into a shadow map and then for real (e.g. Mirror's Edge main menu) 305 | do_copy = counters.current_stats.vertices >= state.best_copy_stats.vertices || (op == clear_op::fullscreen_draw && counters.current_stats.drawcalls >= state.best_copy_stats.drawcalls); 306 | } 307 | else if (std::numeric_limits::max() == depth_stencil_backup->force_clear_index) 308 | { 309 | // Special case for Garry's Mod which chooses the last clear operation that has a high workload 310 | do_copy = counters.current_stats.vertices >= 5000; 311 | } 312 | else 313 | { 314 | // This is not really correct, since clears may accumulate over multiple command lists, but it's unlikely that the same depth-stencil is used in more than one 315 | do_copy = counters.clears.size() == (depth_stencil_backup->force_clear_index - 1); 316 | } 317 | 318 | counters.clears.push_back({ counters.current_stats, op, do_copy }); 319 | } 320 | 321 | // Make a backup copy of the depth texture before it is cleared 322 | if (do_copy) 323 | { 324 | state.best_copy_stats = counters.current_stats; 325 | 326 | // A resource has to be in this state for a clear operation, so can assume it here 327 | cmd_list->barrier(depth_stencil, resource_usage::depth_stencil_write, resource_usage::copy_source); 328 | cmd_list->copy_resource(depth_stencil, depth_stencil_backup->backup_texture); 329 | cmd_list->barrier(depth_stencil, resource_usage::copy_source, resource_usage::depth_stencil_write); 330 | 331 | counters.copied_during_frame = true; 332 | } 333 | } 334 | 335 | // Reset draw call stats for clears 336 | counters.current_stats = { 0, 0 }; 337 | } 338 | 339 | static void update_effect_runtime(effect_runtime *runtime) 340 | { 341 | const generic_depth_data &instance = runtime->get_private_data(); 342 | 343 | runtime->update_texture_bindings("ORIG_DEPTH", instance.selected_shader_resource); 344 | 345 | runtime->enumerate_uniform_variables(nullptr, [&instance](effect_runtime *runtime, auto variable) { 346 | char source[32] = ""; 347 | if (runtime->get_annotation_string_from_uniform_variable(variable, "source", source) && std::strcmp(source, "bufready_depth") == 0) 348 | runtime->set_uniform_value_bool(variable, instance.selected_shader_resource != 0); 349 | }); 350 | 351 | resource_view srv, srv_srgb; 352 | 353 | effect_texture_variable ModifiedDepthTex_handle = runtime->find_texture_variable("Citra.fx", "ModifiedDepthTex"); 354 | runtime->get_texture_binding(ModifiedDepthTex_handle, &srv, &srv_srgb); 355 | 356 | runtime->update_texture_bindings("DEPTH", srv, srv_srgb); 357 | } 358 | 359 | static void on_init_device(device *device) 360 | { 361 | device->create_private_data(); 362 | 363 | reshade::config_get_value(nullptr, "DEPTH", "DisableINTZ", s_disable_intz); 364 | reshade::config_get_value(nullptr, "DEPTH", "DepthCopyBeforeClears", s_preserve_depth_buffers); 365 | reshade::config_get_value(nullptr, "DEPTH", "UseAspectRatioHeuristics", s_use_aspect_ratio_heuristics); 366 | } 367 | static void on_init_command_list(command_list *cmd_list) 368 | { 369 | cmd_list->create_private_data(); 370 | } 371 | static void on_init_command_queue(command_queue *cmd_queue) 372 | { 373 | cmd_queue->create_private_data(); 374 | 375 | if ((cmd_queue->get_type() & command_queue_type::graphics) == 0) 376 | return; 377 | 378 | auto &device_data = cmd_queue->get_device()->get_private_data(); 379 | device_data.queues.push_back(cmd_queue); 380 | } 381 | static void on_init_effect_runtime(effect_runtime *runtime) 382 | { 383 | runtime->create_private_data(); 384 | } 385 | static void on_destroy_device(device *device) 386 | { 387 | auto &device_data = device->get_private_data(); 388 | 389 | // Destroy any remaining resources 390 | for (const auto &[resource, _] : device_data.delayed_destroy_resources) 391 | { 392 | device->destroy_resource(resource); 393 | } 394 | 395 | for (depth_stencil_backup &depth_stencil_backup : device_data.depth_stencil_backups) 396 | { 397 | if (depth_stencil_backup.backup_texture != 0) 398 | device->destroy_resource(depth_stencil_backup.backup_texture); 399 | } 400 | 401 | device->destroy_private_data(); 402 | } 403 | static void on_destroy_command_list(command_list *cmd_list) 404 | { 405 | cmd_list->destroy_private_data(); 406 | } 407 | static void on_destroy_command_queue(command_queue *cmd_queue) 408 | { 409 | cmd_queue->destroy_private_data(); 410 | 411 | auto &device_data = cmd_queue->get_device()->get_private_data(); 412 | device_data.queues.erase(std::remove(device_data.queues.begin(), device_data.queues.end(), cmd_queue), device_data.queues.end()); 413 | } 414 | static void on_destroy_effect_runtime(effect_runtime *runtime) 415 | { 416 | device *const device = runtime->get_device(); 417 | generic_depth_data &data = runtime->get_private_data(); 418 | 419 | if (data.selected_shader_resource != 0) 420 | device->destroy_resource_view(data.selected_shader_resource); 421 | 422 | runtime->destroy_private_data(); 423 | } 424 | 425 | static bool on_create_resource(device *device, resource_desc &desc, subresource_data *, resource_usage) 426 | { 427 | if (desc.type != resource_type::surface && desc.type != resource_type::texture_2d) 428 | return false; // Skip resources that are not 2D textures 429 | if (desc.texture.samples != 1 || (desc.usage & resource_usage::depth_stencil) == 0 || desc.texture.format == format::s8_uint) 430 | return false; // Skip MSAA textures and resources that are not used as depth buffers 431 | 432 | switch (device->get_api()) 433 | { 434 | case device_api::d3d9: 435 | if (s_disable_intz) 436 | return false; 437 | // Skip textures that are sampled as PCF shadow maps (see https://aras-p.info/texts/D3D9GPUHacks.html#shadowmap) using hardware support, since changing format would break that 438 | if (desc.type == resource_type::texture_2d && (desc.texture.format == format::d16_unorm || desc.texture.format == format::d24_unorm_x8_uint || desc.texture.format == format::d24_unorm_s8_uint)) 439 | return false; 440 | // Skip small textures that are likely just shadow maps too (fixes a hang in Dragon's Dogma: Dark Arisen when changing areas) 441 | if (desc.texture.width <= 512) 442 | return false; 443 | // Replace texture format with special format that supports normal sampling (see https://aras-p.info/texts/D3D9GPUHacks.html#depth) 444 | desc.texture.format = format::intz; 445 | desc.usage |= resource_usage::shader_resource; 446 | break; 447 | case device_api::d3d10: 448 | case device_api::d3d11: 449 | // Allow shader access to images that are used as depth-stencil attachments 450 | desc.texture.format = format_to_typeless(desc.texture.format); 451 | desc.usage |= resource_usage::shader_resource; 452 | break; 453 | case device_api::d3d12: 454 | case device_api::vulkan: 455 | // D3D12 and Vulkan always use backup texture, but need to be able to copy to it 456 | desc.usage |= resource_usage::copy_source; 457 | break; 458 | case device_api::opengl: 459 | // No need to change anything in OpenGL 460 | return false; 461 | } 462 | 463 | return true; 464 | } 465 | static bool on_create_resource_view(device *device, resource resource, resource_usage usage_type, resource_view_desc &desc) 466 | { 467 | // A view cannot be created with a typeless format (which was set in 'on_create_resource' above), so fix it in case defaults are used 468 | if ((device->get_api() != device_api::d3d10 && device->get_api() != device_api::d3d11) || desc.format != format::unknown) 469 | return false; 470 | 471 | const resource_desc texture_desc = device->get_resource_desc(resource); 472 | // Only non-MSAA textures where modified, so skip all others 473 | if (texture_desc.texture.samples != 1 || (texture_desc.usage & resource_usage::depth_stencil) == 0) 474 | return false; 475 | 476 | switch (usage_type) 477 | { 478 | case resource_usage::depth_stencil: 479 | desc.format = format_to_depth_stencil_typed(texture_desc.texture.format); 480 | break; 481 | case resource_usage::shader_resource: 482 | desc.format = format_to_default_typed(texture_desc.texture.format); 483 | break; 484 | } 485 | 486 | // Only need to set the rest of the fields if the application did not pass in a valid description already 487 | if (desc.type == resource_view_type::unknown) 488 | { 489 | desc.type = texture_desc.texture.depth_or_layers > 1 ? resource_view_type::texture_2d_array : resource_view_type::texture_2d; 490 | desc.texture.first_level = 0; 491 | desc.texture.level_count = (usage_type == resource_usage::shader_resource) ? UINT32_MAX : 1; 492 | desc.texture.first_layer = 0; 493 | desc.texture.layer_count = (usage_type == resource_usage::shader_resource) ? UINT32_MAX : 1; 494 | } 495 | 496 | return true; 497 | } 498 | static void on_destroy_resource(device *device, resource resource) 499 | { 500 | auto &device_data = device->get_private_data(); 501 | 502 | // In some cases the 'destroy_device' event may be called before all resources have been destroyed 503 | // The state tracking context would have been destroyed already in that case, so return early if it does not exist 504 | if (std::addressof(device_data) == nullptr) 505 | return; 506 | 507 | std::unique_lock lock(s_mutex); 508 | 509 | device_data.destroyed_resources.push_back(resource); 510 | 511 | // Remove this destroyed resource from the list of tracked depth-stencil resources 512 | const auto it = std::find_if(device_data.current_depth_stencil_list.begin(), device_data.current_depth_stencil_list.end(), 513 | [resource](const auto ¤t) { return current.first == resource; }); 514 | if (it != device_data.current_depth_stencil_list.end()) 515 | { 516 | const bool copied_during_frame = it->second.copied_during_frame; 517 | 518 | device_data.current_depth_stencil_list.erase(it); 519 | 520 | lock.unlock(); 521 | 522 | // This is bad ... the resource may still be in use by an effect on the GPU and destroying it would crash it 523 | // Try to mitigate that somehow by delaying this thread a little to hopefully give the GPU enough time to catch up before the resource memory is deallocated 524 | if (device->get_api() == device_api::d3d12 || device->get_api() == device_api::vulkan) 525 | { 526 | reshade::log_message(2, "A depth-stencil resource was destroyed while still being tracked."); 527 | 528 | if (!copied_during_frame) 529 | Sleep(250); 530 | } 531 | } 532 | } 533 | 534 | static bool on_draw(command_list *cmd_list, uint32_t vertices, uint32_t instances, uint32_t, uint32_t) 535 | { 536 | auto &state = cmd_list->get_private_data(); 537 | if (state.current_depth_stencil == 0) 538 | return false; // This is a draw call with no depth-stencil bound 539 | 540 | // Check if this draw call likely represets a fullscreen rectangle (two triangles), which would clear the depth-stencil 541 | const bool fullscreen_draw = vertices == 6 && instances == 1; 542 | if (fullscreen_draw && 543 | s_preserve_depth_buffers == 2 && 544 | state.first_draw_since_bind && 545 | // But ignore that in Vulkan (since it is invalid to copy a resource inside an active render pass) 546 | cmd_list->get_device()->get_api() != device_api::vulkan) 547 | on_clear_depth_impl(cmd_list, state, state.current_depth_stencil, clear_op::fullscreen_draw); 548 | 549 | state.first_draw_since_bind = false; 550 | 551 | depth_stencil_info &counters = state.counters_per_used_depth_stencil[state.current_depth_stencil]; 552 | counters.total_stats.vertices += vertices * instances; 553 | counters.total_stats.drawcalls += 1; 554 | counters.current_stats.vertices += vertices * instances; 555 | counters.current_stats.drawcalls += 1; 556 | 557 | // Skip updating last viewport for fullscreen draw calls, to prevent a clear operation in Prince of Persia: The Sands of Time from getting filtered out 558 | if (!fullscreen_draw) 559 | counters.current_stats.last_viewport = state.current_viewport; 560 | 561 | return false; 562 | } 563 | static bool on_draw_indexed(command_list *cmd_list, uint32_t indices, uint32_t instances, uint32_t, int32_t, uint32_t) 564 | { 565 | on_draw(cmd_list, indices, instances, 0, 0); 566 | 567 | return false; 568 | } 569 | static bool on_draw_indirect(command_list *cmd_list, indirect_command type, resource, uint64_t, uint32_t draw_count, uint32_t) 570 | { 571 | if (type == indirect_command::dispatch) 572 | return false; 573 | 574 | auto &state = cmd_list->get_private_data(); 575 | if (state.current_depth_stencil == 0) 576 | return false; // This is a draw call with no depth-stencil bound 577 | 578 | depth_stencil_info &counters = state.counters_per_used_depth_stencil[state.current_depth_stencil]; 579 | counters.total_stats.drawcalls += draw_count; 580 | counters.total_stats.drawcalls_indirect += draw_count; 581 | counters.current_stats.drawcalls += draw_count; 582 | counters.current_stats.drawcalls_indirect += draw_count; 583 | counters.current_stats.last_viewport = state.current_viewport; 584 | 585 | return false; 586 | } 587 | 588 | static void on_bind_viewport(command_list *cmd_list, uint32_t first, uint32_t count, const viewport *viewport) 589 | { 590 | if (first != 0 || count == 0) 591 | return; // Only interested in the main viewport 592 | 593 | auto &state = cmd_list->get_private_data(); 594 | state.current_viewport = viewport[0]; 595 | } 596 | static void on_bind_depth_stencil(command_list *cmd_list, uint32_t, const resource_view *, resource_view depth_stencil_view) 597 | { 598 | auto &state = cmd_list->get_private_data(); 599 | 600 | const resource depth_stencil = (depth_stencil_view != 0) ? cmd_list->get_device()->get_resource_from_view(depth_stencil_view) : resource{ 0 }; 601 | 602 | if (depth_stencil != state.current_depth_stencil) 603 | { 604 | if (depth_stencil != 0) 605 | state.first_draw_since_bind = true; 606 | 607 | // Make a backup of the depth texture before it is used differently, since in D3D12 or Vulkan the underlying memory may be aliased to a different resource, so cannot just access it at the end of the frame 608 | if (s_preserve_depth_buffers == 2 && 609 | state.current_depth_stencil != 0 && depth_stencil == 0 && ( 610 | cmd_list->get_device()->get_api() == device_api::d3d12 || cmd_list->get_device()->get_api() == device_api::vulkan)) 611 | on_clear_depth_impl(cmd_list, state, state.current_depth_stencil, clear_op::unbind_depth_stencil_view); 612 | } 613 | 614 | state.current_depth_stencil = depth_stencil; 615 | } 616 | static bool on_clear_depth_stencil(command_list *cmd_list, resource_view dsv, const float *depth, const uint8_t *, uint32_t, const rect *) 617 | { 618 | // Ignore clears that do not affect the depth buffer (stencil clears) 619 | if (depth != nullptr && s_preserve_depth_buffers) 620 | { 621 | auto &state = cmd_list->get_private_data(); 622 | 623 | const resource depth_stencil = cmd_list->get_device()->get_resource_from_view(dsv); 624 | 625 | // Note: This does not work when called from 'vkCmdClearAttachments', since it is invalid to copy a resource inside an active render pass 626 | on_clear_depth_impl(cmd_list, state, depth_stencil, clear_op::clear_depth_stencil_view); 627 | } 628 | 629 | return false; 630 | } 631 | static void on_begin_render_pass_with_depth_stencil(command_list *cmd_list, uint32_t, const render_pass_render_target_desc *, const render_pass_depth_stencil_desc *depth_stencil_desc) 632 | { 633 | if (depth_stencil_desc != nullptr && depth_stencil_desc->depth_load_op == render_pass_load_op::clear) 634 | { 635 | on_clear_depth_stencil(cmd_list, depth_stencil_desc->view, &depth_stencil_desc->clear_depth, nullptr, 0, nullptr); 636 | 637 | // Prevent 'on_bind_depth_stencil' from copying depth buffer again 638 | auto &state = cmd_list->get_private_data(); 639 | state.current_depth_stencil = { 0 }; 640 | } 641 | 642 | // If render pass has depth store operation set to 'discard', any copy performed after the render pass will likely contain broken data, so can only hope that the depth buffer can be copied before that ... 643 | 644 | on_bind_depth_stencil(cmd_list, 0, nullptr, depth_stencil_desc != nullptr ? depth_stencil_desc->view : resource_view{}); 645 | } 646 | 647 | static void on_reset(command_list *cmd_list) 648 | { 649 | auto &target_state = cmd_list->get_private_data(); 650 | target_state.reset(); 651 | } 652 | static void on_execute_primary(command_queue *queue, command_list *cmd_list) 653 | { 654 | auto &target_state = queue->get_private_data(); 655 | const auto &source_state = cmd_list->get_private_data(); 656 | 657 | // Skip merging state when this execution event is just the immediate command list getting flushed 658 | if (std::addressof(target_state) != std::addressof(source_state)) 659 | { 660 | target_state.merge(source_state); 661 | } 662 | } 663 | static void on_execute_secondary(command_list *cmd_list, command_list *secondary_cmd_list) 664 | { 665 | auto &target_state = cmd_list->get_private_data(); 666 | const auto &source_state = secondary_cmd_list->get_private_data(); 667 | 668 | // If this is a secondary command list that was recorded without a depth-stencil binding, but is now executed using a depth-stencil binding, handle it as if an indirect draw call was performed to ensure the depth-stencil is tracked 669 | if (target_state.current_depth_stencil != 0 && source_state.current_depth_stencil == 0 && source_state.counters_per_used_depth_stencil.empty()) 670 | { 671 | target_state.current_viewport = source_state.current_viewport; 672 | 673 | on_draw_indirect(cmd_list, indirect_command::draw, { 0 }, 0, 1, 0); 674 | } 675 | else 676 | { 677 | target_state.merge(source_state); 678 | } 679 | } 680 | 681 | static void on_present(command_queue *, swapchain *swapchain, const rect *, const rect *, uint32_t, const rect *) 682 | { 683 | device *const device = swapchain->get_device(); 684 | generic_depth_device_data &device_data = device->get_private_data(); 685 | 686 | const std::unique_lock lock(s_mutex); 687 | 688 | // Merge state from all graphics queues 689 | state_tracking queue_state; 690 | for (command_queue *const queue : device_data.queues) 691 | queue_state.merge(queue->get_private_data()); 692 | 693 | // Only update device list if there are any depth-stencils, otherwise this may be a second present call (at which point 'reset_on_present' already cleared out the queue list in the first present call) 694 | if (queue_state.counters_per_used_depth_stencil.empty()) 695 | return; 696 | 697 | // Also skip update when there has been very little activity (special case for emulators like PCSX2 which may present more often than they render a frame) 698 | if (queue_state.counters_per_used_depth_stencil.size() == 1 && queue_state.counters_per_used_depth_stencil.begin()->second.total_stats.drawcalls <= 8) 699 | return; 700 | 701 | device_data.current_depth_stencil_list.clear(); 702 | device_data.current_depth_stencil_list.reserve(queue_state.counters_per_used_depth_stencil.size()); 703 | 704 | for (const auto &[resource, snapshot] : queue_state.counters_per_used_depth_stencil) 705 | { 706 | if (snapshot.total_stats.drawcalls == 0) 707 | continue; // Skip unused 708 | 709 | if (std::find(device_data.destroyed_resources.begin(), device_data.destroyed_resources.end(), resource) != device_data.destroyed_resources.end()) 710 | continue; // Skip resources that were destroyed by the application 711 | 712 | // Save to current list of depth-stencils on the device, so that it can be displayed in the GUI 713 | device_data.current_depth_stencil_list.emplace_back(resource, snapshot); 714 | } 715 | 716 | for (command_queue *const queue : device_data.queues) 717 | queue->get_private_data().reset_on_present(); 718 | 719 | device_data.destroyed_resources.clear(); 720 | 721 | // Destroy resources that were enqueued for delayed destruction and have reached the targeted number of passed frames 722 | for (auto it = device_data.delayed_destroy_resources.begin(); it != device_data.delayed_destroy_resources.end();) 723 | { 724 | if (--it->second == 0) 725 | { 726 | device->destroy_resource(it->first); 727 | 728 | it = device_data.delayed_destroy_resources.erase(it); 729 | } 730 | else 731 | { 732 | ++it; 733 | } 734 | } 735 | } 736 | 737 | static void on_begin_render_effects(effect_runtime *runtime, command_list *cmd_list, resource_view, resource_view) 738 | { 739 | device *const device = runtime->get_device(); 740 | generic_depth_data &data = runtime->get_private_data(); 741 | generic_depth_device_data &device_data = device->get_private_data(); 742 | 743 | resource best_match = { 0 }; 744 | resource_desc best_match_desc; 745 | const depth_stencil_info *best_snapshot = nullptr; 746 | 747 | uint32_t frame_width, frame_height; 748 | runtime->get_screenshot_width_and_height(&frame_width, &frame_height); 749 | 750 | std::shared_lock lock(s_mutex); 751 | const auto current_depth_stencil_list = device_data.current_depth_stencil_list; 752 | // Unlock while calling into device below, since device may hold a lock itself and that then can deadlock another thread that calls into 'on_destroy_resource' from the device holding that lock 753 | lock.unlock(); 754 | 755 | for (auto &[resource, snapshot] : current_depth_stencil_list) 756 | { 757 | const resource_desc desc = device->get_resource_desc(resource); 758 | if (desc.texture.samples > 1) 759 | continue; // Ignore MSAA textures, since they would need to be resolved first 760 | 761 | if (s_use_aspect_ratio_heuristics && !check_aspect_ratio(static_cast(desc.texture.width), static_cast(desc.texture.height), frame_width, frame_height)) 762 | continue; // Not a good fit 763 | 764 | if (best_snapshot == nullptr || (snapshot.total_stats.drawcalls_indirect < (snapshot.total_stats.drawcalls / 3) ? 765 | // Choose snapshot with the most vertices, since that is likely to contain the main scene 766 | snapshot.total_stats.vertices > best_snapshot->total_stats.vertices : 767 | // Or check draw calls, since vertices may not be accurate if application is using indirect draw calls 768 | snapshot.total_stats.drawcalls > best_snapshot->total_stats.drawcalls)) 769 | { 770 | best_match = resource; 771 | best_match_desc = desc; 772 | best_snapshot = &snapshot; 773 | } 774 | } 775 | 776 | if (data.override_depth_stencil != 0) 777 | { 778 | const auto it = std::find_if(current_depth_stencil_list.begin(), current_depth_stencil_list.end(), 779 | [resource = data.override_depth_stencil](const auto ¤t) { return current.first == resource; }); 780 | if (it != current_depth_stencil_list.end()) 781 | { 782 | best_match = it->first; 783 | best_match_desc = device->get_resource_desc(it->first); 784 | best_snapshot = &it->second; 785 | } 786 | } 787 | 788 | if (best_match != 0) 789 | { 790 | const device_api api = device->get_api(); 791 | 792 | depth_stencil_backup *depth_stencil_backup = device_data.find_depth_stencil_backup(best_match); 793 | 794 | if (best_match != data.selected_depth_stencil || data.selected_shader_resource == 0 || (s_preserve_depth_buffers && depth_stencil_backup == nullptr)) 795 | { 796 | // Destroy previous resource view, since the underlying resource has changed 797 | if (data.selected_shader_resource != 0) 798 | { 799 | runtime->get_command_queue()->wait_idle(); // Ensure resource view is no longer in-use before destroying it 800 | device->destroy_resource_view(data.selected_shader_resource); 801 | 802 | device_data.untrack_depth_stencil(data.selected_depth_stencil); 803 | } 804 | 805 | data.using_backup_texture = false; 806 | data.selected_depth_stencil = best_match; 807 | data.selected_shader_resource = { 0 }; 808 | 809 | // Create two-dimensional resource view to the first level and layer of the depth-stencil resource 810 | resource_view_desc srv_desc(api != device_api::opengl && api != device_api::vulkan ? format_to_default_typed(best_match_desc.texture.format) : best_match_desc.texture.format); 811 | 812 | // Need to create backup texture only if doing backup copies or original resource does not support shader access (which is necessary for binding it to effects) 813 | // Also always create a backup texture in D3D12 or Vulkan to circument problems in case application makes use of resource aliasing 814 | if (s_preserve_depth_buffers || (best_match_desc.usage & resource_usage::shader_resource) == 0 || (api == device_api::d3d12 || api == device_api::vulkan)) 815 | { 816 | depth_stencil_backup = device_data.track_depth_stencil_for_backup(device, best_match, best_match_desc); 817 | 818 | // Abort in case backup texture creation failed 819 | if (depth_stencil_backup->backup_texture == 0) 820 | return; 821 | 822 | depth_stencil_backup->frame_width = frame_width; 823 | depth_stencil_backup->frame_height = frame_height; 824 | 825 | if (s_preserve_depth_buffers) 826 | reshade::config_get_value(nullptr, "DEPTH", "DepthCopyAtClearIndex", depth_stencil_backup->force_clear_index); 827 | else 828 | depth_stencil_backup->force_clear_index = 0; 829 | 830 | if (api == device_api::d3d9) 831 | srv_desc.format = format::r32_float; // Same format as backup texture, as set in 'track_depth_stencil_for_backup' 832 | 833 | if (!device->create_resource_view(depth_stencil_backup->backup_texture, resource_usage::shader_resource, srv_desc, &data.selected_shader_resource)) 834 | return; 835 | 836 | data.using_backup_texture = true; 837 | } 838 | else 839 | { 840 | if (!device->create_resource_view(best_match, resource_usage::shader_resource, srv_desc, &data.selected_shader_resource)) 841 | return; 842 | } 843 | 844 | update_effect_runtime(runtime); 845 | } 846 | 847 | if (data.using_backup_texture) 848 | { 849 | assert(depth_stencil_backup != nullptr && depth_stencil_backup->backup_texture != 0 && best_snapshot != nullptr); 850 | const resource backup_texture = depth_stencil_backup->backup_texture; 851 | 852 | // Copy to backup texture unless already copied during the current frame 853 | if (!best_snapshot->copied_during_frame && (best_match_desc.usage & resource_usage::copy_source) != 0) 854 | { 855 | // Ensure barriers are not created with 'D3D12_RESOURCE_STATE_[...]_SHADER_RESOURCE' when resource has 'D3D12_RESOURCE_FLAG_DENY_SHADER_RESOURCE' flag set 856 | const resource_usage old_state = best_match_desc.usage & (resource_usage::depth_stencil | resource_usage::shader_resource); 857 | 858 | lock.lock(); 859 | const auto it = std::find_if(device_data.current_depth_stencil_list.begin(), device_data.current_depth_stencil_list.end(), 860 | [best_match](const auto ¤t) { return current.first == best_match; }); 861 | // Indicate that the copy is now being done, so it is not repeated in case effects are rendered by another runtime (e.g. when there are multiple present calls in a frame) 862 | if (it != device_data.current_depth_stencil_list.end()) 863 | it->second.copied_during_frame = true; 864 | else 865 | // Resource disappeared from the current depth-stencil list between earlier in this function and now, which indicates that it was destroyed in the meantime 866 | return; 867 | lock.unlock(); 868 | 869 | cmd_list->barrier(best_match, old_state, resource_usage::copy_source); 870 | cmd_list->copy_resource(best_match, backup_texture); 871 | cmd_list->barrier(best_match, resource_usage::copy_source, old_state); 872 | } 873 | 874 | cmd_list->barrier(backup_texture, resource_usage::copy_dest, resource_usage::shader_resource); 875 | } 876 | else 877 | { 878 | // Unset current depth-stencil view, in case it is bound to an effect as a shader resource (which will fail if it is still bound on output) 879 | if (api <= device_api::d3d11) 880 | cmd_list->bind_render_targets_and_depth_stencil(0, nullptr); 881 | 882 | cmd_list->barrier(best_match, resource_usage::depth_stencil | resource_usage::shader_resource, resource_usage::shader_resource); 883 | } 884 | } 885 | else 886 | { 887 | // Unset any existing depth-stencil selected in previous frames 888 | if (data.selected_depth_stencil != 0) 889 | { 890 | if (data.selected_shader_resource != 0) 891 | { 892 | runtime->get_command_queue()->wait_idle(); // Ensure resource view is no longer in-use before destroying it 893 | device->destroy_resource_view(data.selected_shader_resource); 894 | 895 | device_data.untrack_depth_stencil(data.selected_depth_stencil); 896 | } 897 | 898 | data.using_backup_texture = false; 899 | data.selected_depth_stencil = { 0 }; 900 | data.selected_shader_resource = { 0 }; 901 | 902 | update_effect_runtime(runtime); 903 | } 904 | } 905 | } 906 | static void on_finish_render_effects(effect_runtime *runtime, command_list *cmd_list, resource_view, resource_view) 907 | { 908 | const generic_depth_data &data = runtime->get_private_data(); 909 | 910 | if (data.selected_shader_resource != 0) 911 | { 912 | if (data.using_backup_texture) 913 | { 914 | const resource backup_texture = runtime->get_device()->get_resource_from_view(data.selected_shader_resource); 915 | cmd_list->barrier(backup_texture, resource_usage::shader_resource, resource_usage::copy_dest); 916 | } 917 | else 918 | { 919 | cmd_list->barrier(data.selected_depth_stencil, resource_usage::shader_resource, resource_usage::depth_stencil | resource_usage::shader_resource); 920 | } 921 | } 922 | } 923 | 924 | static inline const char *format_to_string(format format) { 925 | switch (format) 926 | { 927 | case format::d16_unorm: 928 | case format::r16_typeless: 929 | return "D16 "; 930 | case format::d16_unorm_s8_uint: 931 | return "D16S8"; 932 | case format::d24_unorm_x8_uint: 933 | return "D24X8"; 934 | case format::d24_unorm_s8_uint: 935 | case format::r24_g8_typeless: 936 | return "D24S8"; 937 | case format::d32_float: 938 | case format::r32_float: 939 | case format::r32_typeless: 940 | return "D32 "; 941 | case format::d32_float_s8_uint: 942 | case format::r32_g8_typeless: 943 | return "D32S8"; 944 | case format::intz: 945 | return "INTZ "; 946 | default: 947 | return " "; 948 | } 949 | } 950 | 951 | static void draw_settings_overlay(effect_runtime *runtime) 952 | { 953 | device *const device = runtime->get_device(); 954 | generic_depth_data &data = runtime->get_private_data(); 955 | generic_depth_device_data &device_data = device->get_private_data(); 956 | 957 | bool force_reset = false; 958 | 959 | if (bool use_aspect_ratio_heuristics = s_use_aspect_ratio_heuristics != 0; 960 | ImGui::Checkbox("Use aspect ratio heuristics", &use_aspect_ratio_heuristics)) 961 | { 962 | s_use_aspect_ratio_heuristics = use_aspect_ratio_heuristics ? 1 : 0; 963 | reshade::config_set_value(nullptr, "DEPTH", "UseAspectRatioHeuristics", s_use_aspect_ratio_heuristics); 964 | force_reset = true; 965 | } 966 | 967 | if (s_use_aspect_ratio_heuristics) 968 | { 969 | if (bool use_aspect_ratio_heuristics_ex = s_use_aspect_ratio_heuristics == 2; 970 | ImGui::Checkbox("Use extended aspect ratio heuristics (for DLSS or resolution scaling)", &use_aspect_ratio_heuristics_ex)) 971 | { 972 | s_use_aspect_ratio_heuristics = use_aspect_ratio_heuristics_ex ? 2 : 1; 973 | reshade::config_set_value(nullptr, "DEPTH", "UseAspectRatioHeuristics", s_use_aspect_ratio_heuristics); 974 | force_reset = true; 975 | } 976 | } 977 | 978 | if (bool copy_before_clear_operations = s_preserve_depth_buffers != 0; 979 | ImGui::Checkbox("Copy depth buffer before clear operations", ©_before_clear_operations)) 980 | { 981 | s_preserve_depth_buffers = copy_before_clear_operations ? 1 : 0; 982 | reshade::config_set_value(nullptr, "DEPTH", "DepthCopyBeforeClears", s_preserve_depth_buffers); 983 | force_reset = true; 984 | } 985 | 986 | const bool is_d3d12_or_vulkan = device->get_api() == device_api::d3d12 || device->get_api() == device_api::vulkan; 987 | 988 | if (s_preserve_depth_buffers || is_d3d12_or_vulkan) 989 | { 990 | if (bool copy_before_fullscreen_draws = s_preserve_depth_buffers == 2; 991 | ImGui::Checkbox(is_d3d12_or_vulkan ? "Copy depth buffer during frame to prevent artifacts" : "Copy depth buffer before fullscreen draw calls", ©_before_fullscreen_draws)) 992 | { 993 | s_preserve_depth_buffers = copy_before_fullscreen_draws ? 2 : 1; 994 | reshade::config_set_value(nullptr, "DEPTH", "DepthCopyBeforeClears", s_preserve_depth_buffers); 995 | } 996 | } 997 | 998 | ImGui::Spacing(); 999 | ImGui::Separator(); 1000 | ImGui::Spacing(); 1001 | 1002 | std::shared_lock lock(s_mutex); 1003 | 1004 | if (device_data.current_depth_stencil_list.empty()) 1005 | { 1006 | ImGui::TextUnformatted("No depth buffers found."); 1007 | return; 1008 | } 1009 | 1010 | // Sort pointer list so that added/removed items do not change the GUI much 1011 | struct depth_stencil_item 1012 | { 1013 | unsigned int display_count; 1014 | resource resource; 1015 | depth_stencil_info snapshot; 1016 | resource_desc desc; 1017 | }; 1018 | 1019 | std::vector sorted_item_list; 1020 | sorted_item_list.reserve(device_data.current_depth_stencil_list.size()); 1021 | 1022 | for (const auto &[resource, snapshot] : device_data.current_depth_stencil_list) 1023 | { 1024 | if (auto it = data.display_count_per_depth_stencil.find(resource); 1025 | it == data.display_count_per_depth_stencil.end()) 1026 | { 1027 | sorted_item_list.push_back({ 1u, resource, snapshot, device->get_resource_desc(resource) }); 1028 | } 1029 | else 1030 | { 1031 | sorted_item_list.push_back({ it->second + 1u, resource, snapshot, device->get_resource_desc(resource) }); 1032 | } 1033 | } 1034 | 1035 | lock.unlock(); 1036 | 1037 | std::sort(sorted_item_list.begin(), sorted_item_list.end(), [](const depth_stencil_item &a, const depth_stencil_item &b) { 1038 | return (a.display_count > b.display_count) || 1039 | (a.display_count == b.display_count && ((a.desc.texture.width > b.desc.texture.width || (a.desc.texture.width == b.desc.texture.width && a.desc.texture.height > b.desc.texture.height)) || 1040 | (a.desc.texture.width == b.desc.texture.width && a.desc.texture.height == b.desc.texture.height && a.resource < b.resource))); 1041 | }); 1042 | 1043 | bool has_msaa_depth_stencil = false; 1044 | bool has_no_clear_operations = false; 1045 | 1046 | data.display_count_per_depth_stencil.clear(); 1047 | for (const depth_stencil_item &item : sorted_item_list) 1048 | { 1049 | data.display_count_per_depth_stencil[item.resource] = item.display_count; 1050 | 1051 | char label[512] = ""; 1052 | sprintf_s(label, "%c 0x%016llx", (item.resource == data.selected_depth_stencil ? '>' : ' '), item.resource.handle); 1053 | 1054 | if (item.desc.texture.samples > 1) // Disable widget for MSAA textures 1055 | { 1056 | has_msaa_depth_stencil = true; 1057 | 1058 | ImGui::BeginDisabled(); 1059 | ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyle().Colors[ImGuiCol_TextDisabled]); 1060 | } 1061 | 1062 | if (bool value = (item.resource == data.override_depth_stencil); 1063 | ImGui::Checkbox(label, &value)) 1064 | { 1065 | data.override_depth_stencil = value ? item.resource : resource{ 0 }; 1066 | force_reset = true; 1067 | } 1068 | 1069 | ImGui::SameLine(); 1070 | ImGui::Text("| %4ux%-4u | %s | %5u draw calls (%5u indirect) ==> %8u vertices |%s", 1071 | item.desc.texture.width, 1072 | item.desc.texture.height, 1073 | format_to_string(item.desc.texture.format), 1074 | item.snapshot.total_stats.drawcalls, 1075 | item.snapshot.total_stats.drawcalls_indirect, 1076 | item.snapshot.total_stats.vertices, 1077 | (item.desc.texture.samples > 1 ? " MSAA" : "")); 1078 | 1079 | if (item.desc.texture.samples > 1) 1080 | { 1081 | ImGui::PopStyleColor(); 1082 | ImGui::EndDisabled(); 1083 | } 1084 | 1085 | if (s_preserve_depth_buffers && item.resource == data.selected_depth_stencil) 1086 | { 1087 | if (item.snapshot.clears.empty()) 1088 | { 1089 | has_no_clear_operations = !is_d3d12_or_vulkan; 1090 | continue; 1091 | } 1092 | 1093 | depth_stencil_backup *const depth_stencil_backup = device_data.find_depth_stencil_backup(item.resource); 1094 | if (depth_stencil_backup == nullptr || depth_stencil_backup->backup_texture == 0) 1095 | continue; 1096 | 1097 | for (size_t clear_index = 1; clear_index <= item.snapshot.clears.size(); ++clear_index) 1098 | { 1099 | const auto &clear_stats = item.snapshot.clears[clear_index - 1]; 1100 | 1101 | sprintf_s(label, "%c CLEAR %2zu", clear_stats.copied_during_frame ? '>' : ' ', clear_index); 1102 | 1103 | if (bool value = (depth_stencil_backup->force_clear_index == clear_index); 1104 | ImGui::Checkbox(label, &value)) 1105 | { 1106 | depth_stencil_backup->force_clear_index = value ? clear_index : 0; 1107 | reshade::config_set_value(nullptr, "DEPTH", "DepthCopyAtClearIndex", depth_stencil_backup->force_clear_index); 1108 | } 1109 | 1110 | ImGui::SameLine(); 1111 | ImGui::Text(" | | | %5u draw calls (%5u indirect) ==> %8u vertices |%s", 1112 | clear_stats.drawcalls, 1113 | clear_stats.drawcalls_indirect, 1114 | clear_stats.vertices, 1115 | clear_stats.clear_op == clear_op::fullscreen_draw ? " Fullscreen draw call" : ""); 1116 | } 1117 | 1118 | if (sorted_item_list.size() == 1 && !is_d3d12_or_vulkan) 1119 | { 1120 | if (bool value = (depth_stencil_backup->force_clear_index == std::numeric_limits::max()); 1121 | ImGui::Checkbox(" Choose last clear operation with high number of draw calls", &value)) 1122 | { 1123 | depth_stencil_backup->force_clear_index = value ? std::numeric_limits::max() : 0; 1124 | reshade::config_set_value(nullptr, "DEPTH", "DepthCopyAtClearIndex", depth_stencil_backup->force_clear_index); 1125 | } 1126 | } 1127 | } 1128 | } 1129 | 1130 | if (has_msaa_depth_stencil || has_no_clear_operations) 1131 | { 1132 | ImGui::Spacing(); 1133 | ImGui::Separator(); 1134 | ImGui::Spacing(); 1135 | 1136 | ImGui::PushTextWrapPos(); 1137 | if (has_msaa_depth_stencil) 1138 | ImGui::TextUnformatted("Not all depth buffers are available.\nYou may have to disable MSAA in the game settings for depth buffer detection to work!"); 1139 | if (has_no_clear_operations) 1140 | ImGui::Text("No clear operations were found for the selected depth buffer.\n%s", 1141 | s_preserve_depth_buffers != 2 ? "Try enabling \"Copy depth buffer before fullscreen draw calls\" or disable \"Copy depth buffer before clear operations\"!" : "Disable \"Copy depth buffer before clear operations\" or select a different depth buffer!"); 1142 | ImGui::PopTextWrapPos(); 1143 | } 1144 | 1145 | if (force_reset) 1146 | { 1147 | // Reset selected depth-stencil to force re-creation of resources next frame (like the backup texture) 1148 | if (data.selected_shader_resource != 0) 1149 | { 1150 | command_queue *const queue = runtime->get_command_queue(); 1151 | 1152 | queue->wait_idle(); // Ensure resource view is no longer in-use before destroying it 1153 | device->destroy_resource_view(data.selected_shader_resource); 1154 | 1155 | device_data.untrack_depth_stencil(data.selected_depth_stencil); 1156 | } 1157 | 1158 | data.using_backup_texture = false; 1159 | data.selected_depth_stencil = { 0 }; 1160 | data.selected_shader_resource = { 0 }; 1161 | 1162 | update_effect_runtime(runtime); 1163 | } 1164 | } 1165 | 1166 | void register_addon_depth() 1167 | { 1168 | reshade::register_overlay(nullptr, draw_settings_overlay); 1169 | 1170 | reshade::register_event(on_init_device); 1171 | reshade::register_event(on_init_command_list); 1172 | reshade::register_event(on_init_command_queue); 1173 | reshade::register_event(on_init_effect_runtime); 1174 | reshade::register_event(on_destroy_device); 1175 | reshade::register_event(on_destroy_command_list); 1176 | reshade::register_event(on_destroy_command_queue); 1177 | reshade::register_event(on_destroy_effect_runtime); 1178 | 1179 | reshade::register_event(on_create_resource); 1180 | reshade::register_event(on_create_resource_view); 1181 | reshade::register_event(on_destroy_resource); 1182 | 1183 | reshade::register_event(on_draw); 1184 | reshade::register_event(on_draw_indexed); 1185 | reshade::register_event(on_draw_indirect); 1186 | reshade::register_event(on_bind_viewport); 1187 | reshade::register_event(on_begin_render_pass_with_depth_stencil); 1188 | reshade::register_event(on_bind_depth_stencil); 1189 | reshade::register_event(on_clear_depth_stencil); 1190 | 1191 | reshade::register_event(on_reset); 1192 | reshade::register_event(on_execute_primary); 1193 | reshade::register_event(on_execute_secondary); 1194 | 1195 | reshade::register_event(on_present); 1196 | 1197 | reshade::register_event(on_begin_render_effects); 1198 | reshade::register_event(on_finish_render_effects); 1199 | // Need to set texture binding again after reloading 1200 | reshade::register_event(update_effect_runtime); 1201 | } 1202 | void unregister_addon_depth() 1203 | { 1204 | reshade::unregister_event(on_init_device); 1205 | reshade::unregister_event(on_init_command_list); 1206 | reshade::unregister_event(on_init_command_queue); 1207 | reshade::unregister_event(on_init_effect_runtime); 1208 | reshade::unregister_event(on_destroy_device); 1209 | reshade::unregister_event(on_destroy_command_list); 1210 | reshade::unregister_event(on_destroy_command_queue); 1211 | reshade::unregister_event(on_destroy_effect_runtime); 1212 | 1213 | reshade::unregister_event(on_create_resource); 1214 | reshade::unregister_event(on_create_resource_view); 1215 | reshade::unregister_event(on_destroy_resource); 1216 | 1217 | reshade::unregister_event(on_draw); 1218 | reshade::unregister_event(on_draw_indexed); 1219 | reshade::unregister_event(on_draw_indirect); 1220 | reshade::unregister_event(on_bind_viewport); 1221 | reshade::unregister_event(on_begin_render_pass_with_depth_stencil); 1222 | reshade::unregister_event(on_bind_depth_stencil); 1223 | reshade::unregister_event(on_clear_depth_stencil); 1224 | 1225 | reshade::unregister_event(on_reset); 1226 | reshade::unregister_event(on_execute_primary); 1227 | reshade::unregister_event(on_execute_secondary); 1228 | 1229 | reshade::unregister_event(on_present); 1230 | 1231 | reshade::unregister_event(on_begin_render_effects); 1232 | reshade::unregister_event(on_finish_render_effects); 1233 | reshade::unregister_event(update_effect_runtime); 1234 | } 1235 | 1236 | extern "C" __declspec(dllexport) const char *NAME = "Citra"; 1237 | extern "C" __declspec(dllexport) const char *DESCRIPTION = "add-on that pre-processes depth buffer from Citra to be standardized / aligned for other add-ons to consume it."; 1238 | 1239 | BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID) 1240 | { 1241 | switch (fdwReason) 1242 | { 1243 | case DLL_PROCESS_ATTACH: 1244 | if (!reshade::register_addon(hinstDLL)) 1245 | return FALSE; 1246 | register_addon_depth(); 1247 | break; 1248 | case DLL_PROCESS_DETACH: 1249 | unregister_addon_depth(); 1250 | reshade::unregister_addon(hinstDLL); 1251 | break; 1252 | } 1253 | return TRUE; 1254 | } 1255 | --------------------------------------------------------------------------------