diff --git a/Data/Sys/Profiles/GraphicsTrigger/Any2D.ini b/Data/Sys/Profiles/GraphicsTrigger/Any2D.ini new file mode 100644 index 000000000000..765b0cb2edba --- /dev/null +++ b/Data/Sys/Profiles/GraphicsTrigger/Any2D.ini @@ -0,0 +1,13 @@ +[Trigger] +Type = DrawCall2D +WidthOperation = 1 +Width = 0 +HeightOperation = 1 +Height = 0 +FormatOperation = 1 +Format = 0 +HashOperation = 1 +Hash = +TlutOperation = 1 +Tlut = +Title = When a 2d object is drawn diff --git a/Data/Sys/Profiles/GraphicsTrigger/EFBFullscreen.ini b/Data/Sys/Profiles/GraphicsTrigger/EFBFullscreen.ini new file mode 100644 index 000000000000..2ef91ff50714 --- /dev/null +++ b/Data/Sys/Profiles/GraphicsTrigger/EFBFullscreen.ini @@ -0,0 +1,9 @@ +[Trigger] +Type = EFB +WidthOperation = 2 +Width = 427 +HeightOperation = 1 +Height = 0 +FormatOperation = 2 +Format = 4,5,6 +Title = When an EFB is processed that takes up most of the screen diff --git a/Data/Sys/Profiles/GraphicsTrigger/Post.ini b/Data/Sys/Profiles/GraphicsTrigger/Post.ini new file mode 100644 index 000000000000..2e958fd15818 --- /dev/null +++ b/Data/Sys/Profiles/GraphicsTrigger/Post.ini @@ -0,0 +1,3 @@ +[Trigger] +Type = Post +Title = After the game is drawn diff --git a/Data/Sys/Shaders/16bit.glsl b/Data/Sys/Shaders/16bit.glsl deleted file mode 100644 index fd13d3469eb7..000000000000 --- a/Data/Sys/Shaders/16bit.glsl +++ /dev/null @@ -1,54 +0,0 @@ -void main() -{ - //Change this number to increase the pixel size. - float pixelSize = 3.0; - - float red = 0.0; - float green = 0.0; - float blue = 0.0; - - float2 pos = floor(GetCoordinates() * GetResolution() / pixelSize) * pixelSize * GetInvResolution(); - - float4 c0 = SampleLocation(pos); - - if (c0.r < 0.1) - red = 0.1; - else if (c0.r < 0.20) - red = 0.20; - else if (c0.r < 0.40) - red = 0.40; - else if (c0.r < 0.60) - red = 0.60; - else if (c0.r < 0.80) - red = 0.80; - else - red = 1.0; - - if (c0.b < 0.1) - blue = 0.1; - else if (c0.b < 0.20) - blue = 0.20; - else if (c0.b < 0.40) - blue = 0.40; - else if (c0.b < 0.60) - blue = 0.60; - else if (c0.b < 0.80) - blue = 0.80; - else - blue = 1.0; - - if (c0.g < 0.1) - green = 0.1; - else if (c0.g < 0.20) - green = 0.20; - else if (c0.g < 0.40) - green = 0.40; - else if (c0.g < 0.60) - green = 0.60; - else if (c0.g < 0.80) - green = 0.80; - else - green = 1.0; - - SetOutput(float4(red, green, blue, c0.a)); -} diff --git a/Data/Sys/Shaders/32bit.glsl b/Data/Sys/Shaders/32bit.glsl deleted file mode 100644 index d0e43db2ce49..000000000000 --- a/Data/Sys/Shaders/32bit.glsl +++ /dev/null @@ -1,79 +0,0 @@ -void main() -{ - //Change this number to increase the pixel size. - float pixelSize = 2.0; - - float red = 0.0; - float green = 0.0; - float blue = 0.0; - - float2 pos = floor(GetCoordinates() * GetResolution() / pixelSize) * pixelSize * GetInvResolution(); - - float4 c0 = SampleLocation(pos); - - if (c0.r < 0.06) - red = 0.06; - else if (c0.r < 0.13) - red = 0.13; - else if (c0.r < 0.26) - red = 0.26; - else if (c0.r < 0.33) - red = 0.33; - else if (c0.r < 0.46) - red = 0.46; - else if (c0.r < 0.60) - red = 0.60; - else if (c0.r < 0.73) - red = 0.73; - else if (c0.r < 0.80) - red = 0.80; - else if (c0.r < 0.93) - red = 0.93; - else - red = 1.0; - - if (c0.b < 0.06) - blue = 0.06; - else if (c0.b < 0.13) - blue = 0.13; - else if (c0.b < 0.26) - blue = 0.26; - else if (c0.b < 0.33) - blue = 0.33; - else if (c0.b < 0.46) - blue = 0.46; - else if (c0.b < 0.60) - blue = 0.60; - else if (c0.b < 0.73) - blue = 0.73; - else if (c0.b < 0.80) - blue = 0.80; - else if( c0.b < 0.93) - blue = 0.93; - else - blue = 1.0; - - - if (c0.g < 0.06) - green = 0.06; - else if (c0.g < 0.13) - green = 0.13; - else if (c0.g < 0.26) - green = 0.26; - else if (c0.g < 0.33) - green = 0.33; - else if (c0.g < 0.46) - green = 0.46; - else if (c0.g < 0.60) - green = 0.60; - else if (c0.g < 0.73) - green = 0.73; - else if (c0.g < 0.80) - green = 0.80; - else if( c0.g < 0.93) - green = 0.93; - else - green = 1.0; - - SetOutput(float4(red, green, blue, c0.a)); -} diff --git a/Data/Sys/Shaders/Anaglyph/dubois-LCD-Amber-Blue.glsl b/Data/Sys/Shaders/Anaglyph/dubois-LCD-Amber-Blue.glsl deleted file mode 100644 index c6410c5c19cc..000000000000 --- a/Data/Sys/Shaders/Anaglyph/dubois-LCD-Amber-Blue.glsl +++ /dev/null @@ -1,20 +0,0 @@ -// Anaglyph Amber-Blue shader based on Dubois algorithm -// Constants taken from the screenshot: -// "https://www.flickr.com/photos/e_dubois/5230654930/" -// Eric Dubois - -void main() -{ - float4 c0 = SampleLayer(0); - float4 c1 = SampleLayer(1); - - float3 lr = float3( 1.062,-0.205, 0.299); - float3 lg = float3(-0.026, 0.908, 0.068); - float3 lb = float3(-0.038,-0.173, 0.022); - - float3 rr = float3(-0.016,-0.123,-0.017); - float3 rg = float3( 0.006, 0.062, 0.017); - float3 rb = float3(-0.094,-0.185, 0.991); - - SetOutput(float4(dot(lr, c0.rgb) + dot(rr, c1.rgb), dot(lg, c0.rgb) + dot(rg, c1.rgb), dot(lb, c0.rgb) + dot(rb, c1.rgb), c0.a)); -} diff --git a/Data/Sys/Shaders/Anaglyph/dubois-LCD-Green-Magenta.glsl b/Data/Sys/Shaders/Anaglyph/dubois-LCD-Green-Magenta.glsl deleted file mode 100644 index bb97bf107b50..000000000000 --- a/Data/Sys/Shaders/Anaglyph/dubois-LCD-Green-Magenta.glsl +++ /dev/null @@ -1,20 +0,0 @@ -// Anaglyph Green-Magenta shader based on Dubois algorithm -// Constants taken from the screenshot: -// "https://www.flickr.com/photos/e_dubois/5132528166/" -// Eric Dubois - -void main() -{ - float4 c0 = SampleLayer(0); - float4 c1 = SampleLayer(1); - - float3 lr = float3(-0.062,-0.158,-0.039); - float3 lg = float3( 0.284, 0.668, 0.143); - float3 lb = float3(-0.015,-0.027, 0.021); - - float3 rr = float3( 0.529, 0.705, 0.024); - float3 rg = float3(-0.016,-0.015, 0.065); - float3 rb = float3( 0.009, 0.075, 0.937); - - SetOutput(float4(dot(lr, c0.rgb) + dot(rr, c1.rgb), dot(lg, c0.rgb) + dot(rg, c1.rgb), dot(lb, c0.rgb) + dot(rb, c1.rgb), c0.a)); -} diff --git a/Data/Sys/Shaders/Anaglyph/dubois.glsl b/Data/Sys/Shaders/Anaglyph/dubois.glsl deleted file mode 100644 index 18c714ddbe04..000000000000 --- a/Data/Sys/Shaders/Anaglyph/dubois.glsl +++ /dev/null @@ -1,21 +0,0 @@ -// Anaglyph Red-Cyan shader based on Dubois algorithm -// Constants taken from the paper: -// "Conversion of a Stereo Pair to Anaglyph with -// the Least-Squares Projection Method" -// Eric Dubois, March 2009 - -void main() -{ - float4 c0 = SampleLayer(0); - float4 c1 = SampleLayer(1); - - float3 lr = float3( 0.437, 0.449, 0.164); - float3 lg = float3(-0.062,-0.062,-0.024); - float3 lb = float3(-0.048,-0.050,-0.017); - - float3 rr = float3(-0.011,-0.032,-0.007); - float3 rg = float3( 0.377, 0.761, 0.009); - float3 rb = float3(-0.026,-0.093, 1.234); - - SetOutput(float4(dot(lr, c0.rgb) + dot(rr, c1.rgb), dot(lg, c0.rgb) + dot(rg, c1.rgb), dot(lb, c0.rgb) + dot(rb, c1.rgb), c0.a)); -} diff --git a/Data/Sys/Shaders/Anaglyph/fullcolor.glsl b/Data/Sys/Shaders/Anaglyph/fullcolor.glsl deleted file mode 100644 index 8f033aedf198..000000000000 --- a/Data/Sys/Shaders/Anaglyph/fullcolor.glsl +++ /dev/null @@ -1,8 +0,0 @@ -// Anaglyph Red-Cyan shader without compensation - -void main() -{ - float4 c0 = SampleLayer(0); - float4 c1 = SampleLayer(1); - SetOutput(float4(c0.r, c1.gb, c0.a)); -} diff --git a/Data/Sys/Shaders/Anaglyph/grayscale.glsl b/Data/Sys/Shaders/Anaglyph/grayscale.glsl deleted file mode 100644 index a1be73da28f3..000000000000 --- a/Data/Sys/Shaders/Anaglyph/grayscale.glsl +++ /dev/null @@ -1,10 +0,0 @@ -// Anaglyph Red-Cyan grayscale shader - -void main() -{ - float4 c0 = SampleLayer(0); - float avg0 = (c0.r + c0.g + c0.b) / 3.0; - float4 c1 = SampleLayer(1); - float avg1 = (c1.r + c1.g + c1.b) / 3.0; - SetOutput(float4(avg0, avg1, avg1, c0.a)); -} diff --git a/Data/Sys/Shaders/Anaglyph/grayscale2.glsl b/Data/Sys/Shaders/Anaglyph/grayscale2.glsl deleted file mode 100644 index 14c506870464..000000000000 --- a/Data/Sys/Shaders/Anaglyph/grayscale2.glsl +++ /dev/null @@ -1,12 +0,0 @@ -// Anaglyph Red-Cyan luma grayscale shader -// Info: https://web.archive.org/web/20040101053504/http://www.oreillynet.com:80/cs/user/view/cs_msg/8691 - -void main() -{ - float3 luma = float3(0.222, 0.707, 0.071); - float4 c0 = SampleLayer(0); - float avg0 = dot(c0.rgb, luma); - float4 c1 = SampleLayer(1); - float avg1 = dot(c1.rgb, luma); - SetOutput(float4(avg0, avg1, avg1, c0.a)); -} diff --git a/Data/Sys/Shaders/FXAA.glsl b/Data/Sys/Shaders/FXAA.glsl deleted file mode 100644 index 897bd30e629f..000000000000 --- a/Data/Sys/Shaders/FXAA.glsl +++ /dev/null @@ -1,67 +0,0 @@ -// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE -// Version 2, December 2004 - -// Copyright (C) 2013 mudlord - -// Everyone is permitted to copy and distribute verbatim or modified -// copies of this license document, and changing it is allowed as long -// as the name is changed. - -// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE -// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - -// 0. You just DO WHAT THE FUCK YOU WANT TO. - -#define FXAA_REDUCE_MIN (1.0/ 128.0) -#define FXAA_REDUCE_MUL (1.0 / 8.0) -#define FXAA_SPAN_MAX 8.0 - -float4 applyFXAA(float2 fragCoord) -{ - float4 color; - float2 inverseVP = GetInvResolution(); - float3 rgbNW = SampleLocation((fragCoord + float2(-1.0, -1.0)) * inverseVP).xyz; - float3 rgbNE = SampleLocation((fragCoord + float2(1.0, -1.0)) * inverseVP).xyz; - float3 rgbSW = SampleLocation((fragCoord + float2(-1.0, 1.0)) * inverseVP).xyz; - float3 rgbSE = SampleLocation((fragCoord + float2(1.0, 1.0)) * inverseVP).xyz; - float3 rgbM = SampleLocation(fragCoord * inverseVP).xyz; - float3 luma = float3(0.299, 0.587, 0.114); - float lumaNW = dot(rgbNW, luma); - float lumaNE = dot(rgbNE, luma); - float lumaSW = dot(rgbSW, luma); - float lumaSE = dot(rgbSE, luma); - float lumaM = dot(rgbM, luma); - float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE))); - float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE))); - - float2 dir; - dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE)); - dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE)); - - float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) * - (0.25 * FXAA_REDUCE_MUL), FXAA_REDUCE_MIN); - - float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce); - dir = min(float2(FXAA_SPAN_MAX, FXAA_SPAN_MAX), - max(float2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX), - dir * rcpDirMin)) * inverseVP; - - float3 rgbA = 0.5 * ( - SampleLocation(fragCoord * inverseVP + dir * (1.0 / 3.0 - 0.5)).xyz + - SampleLocation(fragCoord * inverseVP + dir * (2.0 / 3.0 - 0.5)).xyz); - float3 rgbB = rgbA * 0.5 + 0.25 * ( - SampleLocation(fragCoord * inverseVP + dir * -0.5).xyz + - SampleLocation(fragCoord * inverseVP + dir * 0.5).xyz); - - float lumaB = dot(rgbB, luma); - if ((lumaB < lumaMin) || (lumaB > lumaMax)) - color = float4(rgbA, 1.0); - else - color = float4(rgbB, 1.0); - return color; -} - -void main() -{ - SetOutput(applyFXAA(GetCoordinates() * GetResolution())); -} diff --git a/Data/Sys/Shaders/Internal/DefaultVertexPixelShader.glsl b/Data/Sys/Shaders/Internal/DefaultVertexPixelShader.glsl new file mode 100644 index 000000000000..5dc1951a6d50 --- /dev/null +++ b/Data/Sys/Shaders/Internal/DefaultVertexPixelShader.glsl @@ -0,0 +1,4 @@ +void main() +{ + SetOutput(Sample()); +} diff --git a/Data/Sys/Shaders/Passive/horizontal.glsl b/Data/Sys/Shaders/Passive/horizontal.glsl deleted file mode 100644 index 7b2535bbf091..000000000000 --- a/Data/Sys/Shaders/Passive/horizontal.glsl +++ /dev/null @@ -1,7 +0,0 @@ -// Passive (horizontal rows) shader - -void main() -{ - float screen_row = GetWindowResolution().y * GetCoordinates().y; - SetOutput(SampleLayer(int(screen_row) % 2)); -} diff --git a/Data/Sys/Shaders/Public/Artistic/Outline.glsl b/Data/Sys/Shaders/Public/Artistic/Outline.glsl new file mode 100644 index 000000000000..6f310ef56a13 --- /dev/null +++ b/Data/Sys/Shaders/Public/Artistic/Outline.glsl @@ -0,0 +1,80 @@ +/** + * Depth-buffer based cel shading for ENB by kingeric1992 + * http://enbseries.enbdev.com/forum/viewtopic.php?f=7&t=3244#p53168 + * + * Modified and optimized for ReShade by JPulowski + * http://reshade.me/forum/shader-presentation/261 + * + * Do not distribute without giving credit to the original author(s). + * + * Ported to Dolphin by iwubcode (based off of 1.3) + * + */ + +float3 GetEdgeSample(float2 coord) +{ + if (GetOption(_EdgeDetectionMode) == 1) + { + float4 depth = float4( + SampleDepthLocation(coord + GetInvResolution() * float2(1, 0)), + SampleDepthLocation(coord - GetInvResolution() * float2(1, 0)), + SampleDepthLocation(coord + GetInvResolution() * float2(0, 1)), + SampleDepthLocation(coord - GetInvResolution() * float2(0, 1))); + + float2 depth_buffer_size = float2(SampleInputSize(DEPTH_BUFFER_INPUT_INDEX, 0)); + return normalize(float3(float2(depth.x - depth.y, depth.z - depth.w) * depth_buffer_size, 1.0)); + } + else + { + return SampleLocation(coord).rgb; + } +} + +void main() +{ + float3 color = OptionEnabled(_UseBackgroundColor) ? GetOption(_BackgroundColor) : Sample().rgb; + float3 origcolor = color; + + // Sobel operator matrices + const float3 Gx[3] = + { + float3(-1.0, 0.0, 1.0), + float3(-2.0, 0.0, 2.0), + float3(-1.0, 0.0, 1.0) + }; + const float3 Gy[3] = + { + float3( 1.0, 2.0, 1.0), + float3( 0.0, 0.0, 0.0), + float3(-1.0, -2.0, -1.0) + }; + + float3 dotx = float3(0.0, 0.0, 0.0), doty = float3(0.0, 0.0, 0.0); + + // Edge detection + for (int i = 0, j; i < 3; i++) + { + j = i - 1; + + dotx += Gx[i].x * GetEdgeSample(GetCoordinates() + GetInvResolution() * float2(-1, j)); + dotx += Gx[i].y * GetEdgeSample(GetCoordinates() + GetInvResolution() * float2( 0, j)); + dotx += Gx[i].z * GetEdgeSample(GetCoordinates() + GetInvResolution() * float2( 1, j)); + + doty += Gy[i].x * GetEdgeSample(GetCoordinates() + GetInvResolution() * float2(-1, j)); + doty += Gy[i].y * GetEdgeSample(GetCoordinates() + GetInvResolution() * float2( 0, j)); + doty += Gy[i].z * GetEdgeSample(GetCoordinates() + GetInvResolution() * float2( 1, j)); + } + + // Boost edge detection + dotx *= GetOption(_EdgeDetectionAccuracy); + doty *= GetOption(_EdgeDetectionAccuracy); + + // Return custom color when weight over threshold + float threshold = float(sqrt(dot(dotx, dotx) + dot(doty, doty)) >= GetOption(_EdgeSlope)); + color = lerp(color, GetOption(_OutlineColor).rgb, threshold); + + // Set opacity + color = lerp(origcolor, color, GetOption(_OutlineColor).a); + + SetOutput(float4(color, 1.0)); +} diff --git a/Data/Sys/Shaders/Public/Artistic/Outline.json b/Data/Sys/Shaders/Public/Artistic/Outline.json new file mode 100644 index 000000000000..18ba519dd0c0 --- /dev/null +++ b/Data/Sys/Shaders/Public/Artistic/Outline.json @@ -0,0 +1,74 @@ +{ + "meta": + { + "author": "kingeric1992, JPulowski, iwubcode", + "description": "Ported from Reshade. Add outline" + }, + "options": + [ + { + "type": "bool", + "name": "_UseBackgroundColor", + "ui_name": "UseBackgroundColor", + "default": false + }, + { + "type": "rgb", + "name": "_BackgroundColor", + "ui_name": "BackgroundColor", + "default": [0.0, 0.0, 0.0], + "dependent_option": "_UseBackgroundColor" + }, + { + "type": "rgba", + "name": "_OutlineColor", + "ui_name": "OutlineColor", + "default": [0.0, 0.0, 0.0, 1.0] + }, + { + "type": "float", + "name": "_EdgeSlope", + "ui_name": "EdgeSlope", + "min": [ 0.0 ], + "max": [ 10.0 ], + "step": [ 1.0 ], + "default": [ 1.0 ] + }, + { + "type": "float", + "name": "_EdgeDetectionAccuracy", + "ui_name": "EdgeDetectionAccuracy", + "min": [ 0.0 ], + "max": [ 100.0 ], + "step": [ 1.0 ], + "default": [ 1.0 ] + }, + { + "type": "enum", + "name": "_EdgeDetectionMode", + "ui_name": "EdgeDetectionMode", + "ui_choices": [ "Color", "Depth" ], + "values": [ 0.0, 1.0 ], + "default_index": 0.0 + } + ], + "passes": + [ + { + "entry_point": "main", + "inputs": + [ + { + "type": "previous_pass", + "texture_filter": "linear", + "texture_mode": "clamp" + }, + { + "type": "depth_buffer", + "texture_filter": "point", + "texture_mode": "clamp" + } + ] + } + ] +} \ No newline at end of file diff --git a/Data/Sys/Shaders/Public/Artistic/Paint.glsl b/Data/Sys/Shaders/Public/Artistic/Paint.glsl new file mode 100644 index 000000000000..cdd26b8604a7 --- /dev/null +++ b/Data/Sys/Shaders/Public/Artistic/Paint.glsl @@ -0,0 +1,144 @@ +/** + * Basic kuwahara filtering by Jan Eric Kyprianidis + * https://code.google.com/p/gpuakf/source/browse/glsl/kuwahara.glsl + * + * Copyright (C) 2009-2011 Computer Graphics Systems Group at the + * Hasso-Plattner-Institut, Potsdam, Germany + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Paint effect shader for ENB by kingeric1992 + * http://enbseries.enbdev.com/forum/viewtopic.php?f=7&t=3244#p53168 + * + * Modified and optimized for ReShade by JPulowski + * https://reshade.me/forum/shader-presentation/261 + * + * Do not distribute without giving credit to the original author(s). + * + * Ported to Dolphin by iwubcode (based off of 1.3) + */ + +void Kuwahara() +{ + int paint_radius = GetOption(_PaintRadius); + float n = pow(paint_radius, -2.0); + float4 col = float4(1.0, 1.0, 1.0, 1.0); + + // Using vectors instead of arrays, otherwise temp register index gets exceeded very quickly + float4 m0 = float4(0.0, 0.0, 0.0, 0.0); + float4 m1 = float4(0.0, 0.0, 0.0, 0.0); + float4 m2 = float4(0.0, 0.0, 0.0, 0.0); + float4 m3 = float4(0.0, 0.0, 0.0, 0.0); + float3 s1 = float3(0.0, 0.0, 0.0); + float3 s2 = float3(0.0, 0.0, 0.0); + float3 s3 = float3(0.0, 0.0, 0.0); + float3 s4 = float3(0.0, 0.0, 0.0); + + for (int i = 0; i < paint_radius; i++) { + for (int j = 0; j < paint_radius; j++) { + col.rgb = SampleLocation(GetCoordinates() + GetInvResolution() * float2(-i, -j)).rgb; + m0.rgb += col.rgb; + s1 += col.rgb * col.rgb; + + col.rgb = SampleLocation(GetCoordinates() + GetInvResolution() * float2( i, -j)).rgb; + m1.rgb += col.rgb; + s2 += col.rgb * col.rgb; + + col.rgb = SampleLocation(GetCoordinates() + GetInvResolution() * float2( i, j)).rgb; + m2.rgb += col.rgb; + s3 += col.rgb * col.rgb; + + col.rgb = SampleLocation(GetCoordinates() + GetInvResolution() * float2(-i, j)).rgb; + m3.rgb += col.rgb; + s4 += col.rgb * col.rgb; + } + } + + m0.rgb *= n; m1.rgb *= n; + m2.rgb *= n; m3.rgb *= n; + + // Sigma2 + m0.a = dot(distance(s1.rgb * n, m0.rgb * m0.rgb), 1.0); + m1.a = dot(distance(s2.rgb * n, m1.rgb * m1.rgb), 1.0); + m2.a = dot(distance(s3.rgb * n, m2.rgb * m2.rgb), 1.0); + m3.a = dot(distance(s4.rgb * n, m3.rgb * m3.rgb), 1.0); + + col = m0.a < col.a ? m0 : col; + col = m1.a < col.a ? m1 : col; + col = m2.a < col.a ? m2 : col; + col = m3.a < col.a ? m3 : col; + + SetOutput(float4(col.rgb, 1.0)); +} + +void Paint() +{ + float4 col; + float3 lumcoeff = float3(0.2126, 0.7152, 0.0722) * 9.0; // Multiplied by total number of available intensity levels + + float4 col0 = float4(0.0, 0.0f, 0.0f, 0.0f); + float4 col1 = float4(0.0, 0.0f, 0.0f, 0.0f); + float4 col2 = float4(0.0, 0.0f, 0.0f, 0.0f); + float4 col3 = float4(0.0, 0.0f, 0.0f, 0.0f); + float4 col4 = float4(0.0, 0.0f, 0.0f, 0.0f); + float4 col5 = float4(0.0, 0.0f, 0.0f, 0.0f); + float4 col6 = float4(0.0, 0.0f, 0.0f, 0.0f); + float4 col7 = float4(0.0, 0.0f, 0.0f, 0.0f); + float4 col8 = float4(0.0, 0.0f, 0.0f, 0.0f); + float4 col9 = float4(0.0, 0.0f, 0.0f, 0.0f); + + int paint_radius = GetOption(_PaintRadius); + + for (int i = -paint_radius; i <= paint_radius; i++) { + for (int j = -paint_radius; j <= paint_radius; j++) { + col.rgb = SampleLocation(GetCoordinates() + GetInvResolution() * float2(i, j)).rgb; + col.a = round(dot(col.rgb, lumcoeff)) + 1.0; // Store intensity in alpha channel and increase it by 1, so we can count + // values between 0.0 - 1.0 + col0 += col.a == 1.0 ? col : float4(0.0, 0.0f, 0.0f, 0.0f); + col1 += col.a == 2.0 ? col : float4(0.0, 0.0f, 0.0f, 0.0f); + col2 += col.a == 3.0 ? col : float4(0.0, 0.0f, 0.0f, 0.0f); + col3 += col.a == 4.0 ? col : float4(0.0, 0.0f, 0.0f, 0.0f); + col4 += col.a == 5.0 ? col : float4(0.0, 0.0f, 0.0f, 0.0f); + col5 += col.a == 6.0 ? col : float4(0.0, 0.0f, 0.0f, 0.0f); + col6 += col.a == 7.0 ? col : float4(0.0, 0.0f, 0.0f, 0.0f); + col7 += col.a == 8.0 ? col : float4(0.0, 0.0f, 0.0f, 0.0f); + col8 += col.a == 9.0 ? col : float4(0.0, 0.0f, 0.0f, 0.0f); + col9 += col.a == 10.0 ? col : float4(0.0, 0.0f, 0.0f, 0.0f); + } + } + + // Calculate intensity count + col1.a /= 2.0; + col2.a /= 3.0; + col3.a /= 4.0; + col4.a /= 5.0; + col5.a /= 6.0; + col6.a /= 7.0; + col7.a /= 8.0; + col8.a /= 9.0; + col9.a /= 10.0; + + col.a = 0.0; + + // Calculate mode + col = col0.a > col.a ? col0 : col; + col = col1.a > col.a ? col1 : col; + col = col2.a > col.a ? col2 : col; + col = col3.a > col.a ? col3 : col; + col = col4.a > col.a ? col4 : col; + col = col5.a > col.a ? col5 : col; + col = col6.a > col.a ? col6 : col; + col = col7.a > col.a ? col7 : col; + col = col8.a > col.a ? col8 : col; + col = col9.a > col.a ? col9 : col; + + SetOutput(float4(col.rgb / col.a, 1.0f)); +} diff --git a/Data/Sys/Shaders/Public/Artistic/Paint.json b/Data/Sys/Shaders/Public/Artistic/Paint.json new file mode 100644 index 000000000000..5bd918d61ec0 --- /dev/null +++ b/Data/Sys/Shaders/Public/Artistic/Paint.json @@ -0,0 +1,44 @@ +{ + "meta": + { + "author": "iwubcode, others", + "description": "Ported from reshade. Basic kuwahara filtering. Very intensive at larger values. Beware!" + }, + "options": + [ + { + "type": "int", + "name": "_PaintRadius", + "ui_name": "PaintRadius", + "min": [ 0.0 ], + "max": [ 20.0 ], + "step": [ 1.0 ], + "default": [ 2.0 ] + } + ], + "passes": + [ + { + "entry_point": "Kuwahara", + "inputs": + [ + { + "type": "previous_pass", + "texture_filter": "linear", + "texture_mode": "clamp" + } + ] + }, + { + "entry_point": "Paint", + "inputs": + [ + { + "type": "previous_pass", + "texture_filter": "linear", + "texture_mode": "clamp" + } + ] + } + ] +} \ No newline at end of file diff --git a/Data/Sys/Shaders/emboss.glsl b/Data/Sys/Shaders/Public/Artistic/emboss.glsl similarity index 100% rename from Data/Sys/Shaders/emboss.glsl rename to Data/Sys/Shaders/Public/Artistic/emboss.glsl diff --git a/Data/Sys/Shaders/sketchy.glsl b/Data/Sys/Shaders/Public/Artistic/sketchy.glsl similarity index 100% rename from Data/Sys/Shaders/sketchy.glsl rename to Data/Sys/Shaders/Public/Artistic/sketchy.glsl diff --git a/Data/Sys/Shaders/acidmetal.glsl b/Data/Sys/Shaders/Public/Color/acidmetal.glsl similarity index 100% rename from Data/Sys/Shaders/acidmetal.glsl rename to Data/Sys/Shaders/Public/Color/acidmetal.glsl diff --git a/Data/Sys/Shaders/acidtrip.glsl b/Data/Sys/Shaders/Public/Color/acidtrip.glsl similarity index 100% rename from Data/Sys/Shaders/acidtrip.glsl rename to Data/Sys/Shaders/Public/Color/acidtrip.glsl diff --git a/Data/Sys/Shaders/acidtrip2.glsl b/Data/Sys/Shaders/Public/Color/acidtrip2.glsl similarity index 100% rename from Data/Sys/Shaders/acidtrip2.glsl rename to Data/Sys/Shaders/Public/Color/acidtrip2.glsl diff --git a/Data/Sys/Shaders/Public/Color/apply_lut.glsl b/Data/Sys/Shaders/Public/Color/apply_lut.glsl new file mode 100644 index 000000000000..cb969dd0e784 --- /dev/null +++ b/Data/Sys/Shaders/Public/Color/apply_lut.glsl @@ -0,0 +1,72 @@ +/* +ported from https://github.com/Fubaxiusz/fubax-shaders/blob/master/Shaders/LUTTools.fx + +Apply LUT PS v2.0.0 (c) 2018 Jacob Maximilian Fober, +(remix of LUT shader 1.0 (c) 2016 Marty McFly) +This work is licensed under the Creative Commons +Attribution-ShareAlike 4.0 International License. +To view a copy of this license, visit +http://creativecommons.org/licenses/by-sa/4.0/. +*/ + +#if LUT_VERTICAL == 1 + #define LUT_DIMENSIONS int2(LUT_BLOCK_SIZE, LUT_BLOCK_SIZE*LUT_BLOCK_SIZE) +#else + #define LUT_DIMENSIONS int2(LUT_BLOCK_SIZE*LUT_BLOCK_SIZE, LUT_BLOCK_SIZE) +#endif +#define LUT_PIXEL_SIZE 1.0/LUT_DIMENSIONS + +void main() +{ + float3 color = Sample().rgb; + + // Convert to sub pixel coordinates + float3 lut3D = color*(LUT_BLOCK_SIZE-1); + + // Get 2D LUT coordinates + float2 lut2D[2]; + #if LUT_VERTICAL == 1 + // Front + lut2D[0].x = lut3D.x; + lut2D[0].y = floor(lut3D.z)*LUT_BLOCK_SIZE+lut3D.y; + // Back + lut2D[1].x = lut3D.x; + lut2D[1].y = ceil(lut3D.z)*LUT_BLOCK_SIZE+lut3D.y; + #else + // Front + lut2D[0].x = floor(lut3D.z)*LUT_BLOCK_SIZE+lut3D.x; + lut2D[0].y = lut3D.y; + // Back + lut2D[1].x = ceil(lut3D.z)*LUT_BLOCK_SIZE+lut3D.x; + lut2D[1].y = lut3D.y; + #endif + + // Convert from texel to texture coords + lut2D[0] = (lut2D[0]+0.5)*LUT_PIXEL_SIZE; + lut2D[1] = (lut2D[1]+0.5)*LUT_PIXEL_SIZE; + + // Bicubic LUT interpolation + float3 color_from_table = mix( + SampleInputLocation(1, lut2D[0]).rgb, // Front Z + SampleInputLocation(1, lut2D[1]).rgb, // Back Z + frac(lut3D.z) + ); + + // Blend LUT image with original + if ( _LutChromaLuma.x == 1.0 && _LutChromaLuma.y == 1.0 ) + color = color_from_table; + else + { + color = mix( + normalize(color), + normalize(color_from_table), + _LutChromaLuma.x + )*mix( + length(color), + length(color_from_table), + _LutChromaLuma.y + ); + } + + SetOutput(float4(color, 1.0)); +} diff --git a/Data/Sys/Shaders/Public/Color/apply_lut.json b/Data/Sys/Shaders/Public/Color/apply_lut.json new file mode 100644 index 000000000000..1ac299b2cf02 --- /dev/null +++ b/Data/Sys/Shaders/Public/Color/apply_lut.json @@ -0,0 +1,54 @@ +{ + "meta": + { + "author": "iwubcode, Fubaxiusz", + "description": "A shader that applies a color LUT (lookup-table)" + }, + "options": + [ + { + "type": "float2", + "name": "_LutChromaLuma", + "ui_name": "LutChromaLuma", + "min": [ 0.0, 0.0 ], + "max": [ 1.0, 1.0 ], + "step": [ 0.005, 0.005 ], + "default": [ 1.0, 1.0 ] + }, + { + "type": "int", + "name": "LUT_BLOCK_SIZE", + "ui_name": "LUT Block Size", + "min": [ 16.0 ], + "max": [ 64.0 ], + "default": [ 32.0 ], + "is_constant": true + }, + { + "type": "bool", + "name": "LUT_VERTICAL", + "ui_name": "LUT Vertical", + "default": false, + "is_constant": true + } + ], + "passes": + [ + { + "entry_point": "main", + "inputs": + [ + { + "type": "previous_pass", + "texture_filter": "linear", + "texture_mode": "clamp" + }, + { + "type": "user_image", + "texture_filter": "point", + "texture_mode": "clamp" + } + ] + } + ] +} \ No newline at end of file diff --git a/Data/Sys/Shaders/chrismas.glsl b/Data/Sys/Shaders/Public/Color/chrismas.glsl similarity index 100% rename from Data/Sys/Shaders/chrismas.glsl rename to Data/Sys/Shaders/Public/Color/chrismas.glsl diff --git a/Data/Sys/Shaders/cool1.glsl b/Data/Sys/Shaders/Public/Color/cool1.glsl similarity index 100% rename from Data/Sys/Shaders/cool1.glsl rename to Data/Sys/Shaders/Public/Color/cool1.glsl diff --git a/Data/Sys/Shaders/Public/Color/draw_lut.glsl b/Data/Sys/Shaders/Public/Color/draw_lut.glsl new file mode 100644 index 000000000000..092c3bdac507 --- /dev/null +++ b/Data/Sys/Shaders/Public/Color/draw_lut.glsl @@ -0,0 +1,42 @@ +/* +ported from https://github.com/Fubaxiusz/fubax-shaders/blob/master/Shaders/LUTTools.fx + +Display LUT PS v1.3.3 (c) 2018 Jacob Maximilian Fober; +(remix of LUT shader 1.0 (c) 2016 Marty McFly) +This work is licensed under the Creative Commons +Attribution-ShareAlike 4.0 International License. +To view a copy of this license, visit +http://creativecommons.org/licenses/by-sa/4.0/. +*/ + +void main() +{ + float2 LutBounds; + #if LUT_VERTICAL == 1 + LutBounds = float2(_LutSize, _LutSize*_LutSize); + #else + LutBounds = float2(_LutSize*_LutSize, _LutSize); + #endif + LutBounds *= GetInvPrevResolution(); + + if( GetCoordinates().x >= LutBounds.x || GetCoordinates().y >= LutBounds.y) + { + SetOutput(float4(Sample().rgb, 1.0)); + } + else + { + // Generate pattern UV + float2 pattern = GetCoordinates()*GetPrevResolution()/_LutSize; + // Convert pattern to RGB LUT + float3 color; + color.rg = frac(pattern)-0.5/_LutSize; + color.rg /= 1.0-1.0/_LutSize; +#if LUT_VERTICAL == 1 + color.b = floor(pattern.g)/(_LutSize-1); +#else + color.b = floor(pattern.r)/(_LutSize-1); +#endif + // Display LUT texture + SetOutput(float4(color, 1.0)); + } +} diff --git a/Data/Sys/Shaders/Public/Color/draw_lut.json b/Data/Sys/Shaders/Public/Color/draw_lut.json new file mode 100644 index 000000000000..813f80fe6391 --- /dev/null +++ b/Data/Sys/Shaders/Public/Color/draw_lut.json @@ -0,0 +1,39 @@ +{ + "meta": + { + "author": "iwubcode, Fubaxiusz", + "description": "A shader that applies a color LUT (lookup-table)" + }, + "options": + [ + { + "type": "int", + "name": "_LutSize", + "ui_name": "LUT Block Size", + "min": [ 16.0 ], + "max": [ 64.0 ], + "default": [ 32.0 ] + }, + { + "type": "bool", + "name": "LUT_VERTICAL", + "ui_name": "LUT Vertical", + "default": false, + "is_constant": true + } + ], + "passes": + [ + { + "entry_point": "main", + "inputs": + [ + { + "type": "previous_pass", + "texture_filter": "linear", + "texture_mode": "clamp" + } + ] + } + ] +} \ No newline at end of file diff --git a/Data/Sys/Shaders/fire.glsl b/Data/Sys/Shaders/Public/Color/fire.glsl similarity index 100% rename from Data/Sys/Shaders/fire.glsl rename to Data/Sys/Shaders/Public/Color/fire.glsl diff --git a/Data/Sys/Shaders/fire2.glsl b/Data/Sys/Shaders/Public/Color/fire2.glsl similarity index 100% rename from Data/Sys/Shaders/fire2.glsl rename to Data/Sys/Shaders/Public/Color/fire2.glsl diff --git a/Data/Sys/Shaders/firewater.glsl b/Data/Sys/Shaders/Public/Color/firewater.glsl similarity index 100% rename from Data/Sys/Shaders/firewater.glsl rename to Data/Sys/Shaders/Public/Color/firewater.glsl diff --git a/Data/Sys/Shaders/grayscale.glsl b/Data/Sys/Shaders/Public/Color/grayscale.glsl similarity index 100% rename from Data/Sys/Shaders/grayscale.glsl rename to Data/Sys/Shaders/Public/Color/grayscale.glsl diff --git a/Data/Sys/Shaders/grayscale2.glsl b/Data/Sys/Shaders/Public/Color/grayscale2.glsl similarity index 100% rename from Data/Sys/Shaders/grayscale2.glsl rename to Data/Sys/Shaders/Public/Color/grayscale2.glsl diff --git a/Data/Sys/Shaders/invert.glsl b/Data/Sys/Shaders/Public/Color/invert.glsl similarity index 100% rename from Data/Sys/Shaders/invert.glsl rename to Data/Sys/Shaders/Public/Color/invert.glsl diff --git a/Data/Sys/Shaders/invert_blue.glsl b/Data/Sys/Shaders/Public/Color/invert_blue.glsl similarity index 100% rename from Data/Sys/Shaders/invert_blue.glsl rename to Data/Sys/Shaders/Public/Color/invert_blue.glsl diff --git a/Data/Sys/Shaders/mad_world.glsl b/Data/Sys/Shaders/Public/Color/mad_world.glsl similarity index 100% rename from Data/Sys/Shaders/mad_world.glsl rename to Data/Sys/Shaders/Public/Color/mad_world.glsl diff --git a/Data/Sys/Shaders/nightvision.glsl b/Data/Sys/Shaders/Public/Color/nightvision.glsl similarity index 100% rename from Data/Sys/Shaders/nightvision.glsl rename to Data/Sys/Shaders/Public/Color/nightvision.glsl diff --git a/Data/Sys/Shaders/nightvision2.glsl b/Data/Sys/Shaders/Public/Color/nightvision2.glsl similarity index 100% rename from Data/Sys/Shaders/nightvision2.glsl rename to Data/Sys/Shaders/Public/Color/nightvision2.glsl diff --git a/Data/Sys/Shaders/nightvision2scanlines.glsl b/Data/Sys/Shaders/Public/Color/nightvision2scanlines.glsl similarity index 100% rename from Data/Sys/Shaders/nightvision2scanlines.glsl rename to Data/Sys/Shaders/Public/Color/nightvision2scanlines.glsl diff --git a/Data/Sys/Shaders/primarycolors.glsl b/Data/Sys/Shaders/Public/Color/primarycolors.glsl similarity index 100% rename from Data/Sys/Shaders/primarycolors.glsl rename to Data/Sys/Shaders/Public/Color/primarycolors.glsl diff --git a/Data/Sys/Shaders/sepia.glsl b/Data/Sys/Shaders/Public/Color/sepia.glsl similarity index 100% rename from Data/Sys/Shaders/sepia.glsl rename to Data/Sys/Shaders/Public/Color/sepia.glsl diff --git a/Data/Sys/Shaders/spookey1.glsl b/Data/Sys/Shaders/Public/Color/spookey1.glsl similarity index 100% rename from Data/Sys/Shaders/spookey1.glsl rename to Data/Sys/Shaders/Public/Color/spookey1.glsl diff --git a/Data/Sys/Shaders/spookey2.glsl b/Data/Sys/Shaders/Public/Color/spookey2.glsl similarity index 100% rename from Data/Sys/Shaders/spookey2.glsl rename to Data/Sys/Shaders/Public/Color/spookey2.glsl diff --git a/Data/Sys/Shaders/sunset.glsl b/Data/Sys/Shaders/Public/Color/sunset.glsl similarity index 100% rename from Data/Sys/Shaders/sunset.glsl rename to Data/Sys/Shaders/Public/Color/sunset.glsl diff --git a/Data/Sys/Shaders/swap_RGB_BGR.glsl b/Data/Sys/Shaders/Public/Color/swap_RGB_BGR.glsl similarity index 100% rename from Data/Sys/Shaders/swap_RGB_BGR.glsl rename to Data/Sys/Shaders/Public/Color/swap_RGB_BGR.glsl diff --git a/Data/Sys/Shaders/swap_RGB_BRG.glsl b/Data/Sys/Shaders/Public/Color/swap_RGB_BRG.glsl similarity index 100% rename from Data/Sys/Shaders/swap_RGB_BRG.glsl rename to Data/Sys/Shaders/Public/Color/swap_RGB_BRG.glsl diff --git a/Data/Sys/Shaders/Public/Color/swap_RGB_GBR.glsl b/Data/Sys/Shaders/Public/Color/swap_RGB_GBR.glsl new file mode 100644 index 000000000000..5dc1951a6d50 --- /dev/null +++ b/Data/Sys/Shaders/Public/Color/swap_RGB_GBR.glsl @@ -0,0 +1,4 @@ +void main() +{ + SetOutput(Sample()); +} diff --git a/Data/Sys/Shaders/Public/Color/swap_RGB_GRB.glsl b/Data/Sys/Shaders/Public/Color/swap_RGB_GRB.glsl new file mode 100644 index 000000000000..5dc1951a6d50 --- /dev/null +++ b/Data/Sys/Shaders/Public/Color/swap_RGB_GRB.glsl @@ -0,0 +1,4 @@ +void main() +{ + SetOutput(Sample()); +} diff --git a/Data/Sys/Shaders/swap_RGB_RBG.glsl b/Data/Sys/Shaders/Public/Color/swap_RGB_RBG.glsl similarity index 100% rename from Data/Sys/Shaders/swap_RGB_RBG.glsl rename to Data/Sys/Shaders/Public/Color/swap_RGB_RBG.glsl diff --git a/Data/Sys/Shaders/toxic.glsl b/Data/Sys/Shaders/Public/Color/toxic.glsl similarity index 100% rename from Data/Sys/Shaders/toxic.glsl rename to Data/Sys/Shaders/Public/Color/toxic.glsl diff --git a/Data/Sys/Shaders/auto_toon.glsl b/Data/Sys/Shaders/auto_toon.glsl deleted file mode 100644 index d4fd1a0bcf3e..000000000000 --- a/Data/Sys/Shaders/auto_toon.glsl +++ /dev/null @@ -1,15 +0,0 @@ -void main() -{ - float4 to_gray = float4(0.3,0.59,0.11,0); - - float x1 = dot(to_gray, SampleOffset(int2( 1, 1))); - float x0 = dot(to_gray, SampleOffset(int2(-1,-1))); - float x3 = dot(to_gray, SampleOffset(int2( 1,-1))); - float x2 = dot(to_gray, SampleOffset(int2(-1, 1))); - - float edge = (x1 - x0) * (x1 - x0) + (x3 - x2) * (x3 - x2); - - float4 color = Sample(); - - SetOutput(color - float4(edge, edge, edge, edge) * 12.0); -} diff --git a/Data/Sys/Shaders/auto_toon2.glsl b/Data/Sys/Shaders/auto_toon2.glsl deleted file mode 100644 index 5f12ec155988..000000000000 --- a/Data/Sys/Shaders/auto_toon2.glsl +++ /dev/null @@ -1,88 +0,0 @@ -void main() -{ - //Changethis to increase the number of colors. - int numColors =8; - - float4 to_gray = float4(0.3,0.59,0.11,0); - float x1 = dot(to_gray, SampleOffset(int2( 1, 1))); - float x0 = dot(to_gray, SampleOffset(int2(-1,-1))); - float x3 = dot(to_gray, SampleOffset(int2( 1,-1))); - float x2 = dot(to_gray, SampleOffset(int2(-1, 1))); - float edge = (x1 - x0) * (x1 - x0) + (x3 - x2) * (x3 - x2); - float4 color = Sample(); - - float4 c0 = color - float4(edge, edge, edge, edge) * 12.0; - - float red = 0.0; - float green = 0.0; - float blue = 0.0; - bool rr = false; - bool bb = false; - bool gg = false; - int count = 1; - - float colorN = 0.0; - float colorB = 0.0; - - for (count = 1; count <= numColors; count++) - { - colorN = float(count / numColors); - - if ( c0.r <= colorN && c0.r >= colorB && rr == false ) - { - if (count == 1) - { - if (colorN >= 0.1) - red = 0.01; - else - red = colorN; - } - else if (count == numColors) - red = 0.95; - else - red = colorN; - - rr = true; - } - - if (c0.b <= colorN && c0.b >= colorB && bb == false) - { - if (count == 1) - { - if (colorN >= 0.1) - blue = 0.01; - else - blue = colorN; - } - else if (count == numColors) - blue = 0.95; - else - blue = colorN ; - - bb = true; - } - - if (c0.g <= colorN && c0.g >= colorB && gg == false) - { - if (count == 1) - { - if (colorN >= 0.1) - green = 0.01; - else - green = colorN; - } - else if (count == numColors) - green = 0.95; - else - green = colorN; - gg = true; - } - - colorB = float(count / numColors); - - if (rr == true && bb == true && gg == true) - break; - } - - SetOutput(float4(red, green, blue, c0.a)); -} diff --git a/Data/Sys/Shaders/bad_bloom.glsl b/Data/Sys/Shaders/bad_bloom.glsl deleted file mode 100644 index f39e6fcdfee2..000000000000 --- a/Data/Sys/Shaders/bad_bloom.glsl +++ /dev/null @@ -1,36 +0,0 @@ -void main() -{ - float4 c_center = Sample(); - - float4 bloom_sum = float4(0.0, 0.0, 0.0, 0.0); - float2 pos = GetCoordinates() + float2(0.3, 0.3) * GetInvResolution(); - float2 radius1 = 1.3 * GetInvResolution(); - bloom_sum += SampleLocation(pos + float2(-1.5, -1.5) * radius1); - bloom_sum += SampleLocation(pos + float2(-2.5, 0.0) * radius1); - bloom_sum += SampleLocation(pos + float2(-1.5, 1.5) * radius1); - bloom_sum += SampleLocation(pos + float2(0.0, 2.5) * radius1); - bloom_sum += SampleLocation(pos + float2(1.5, 1.5) * radius1); - bloom_sum += SampleLocation(pos + float2(2.5, 0.0) * radius1); - bloom_sum += SampleLocation(pos + float2(1.5, -1.5) * radius1); - bloom_sum += SampleLocation(pos + float2(0.0, -2.5) * radius1); - - float2 radius2 = 4.6 * GetInvResolution(); - bloom_sum += SampleLocation(pos + float2(-1.5, -1.5) * radius2); - bloom_sum += SampleLocation(pos + float2(-2.5, 0.0) * radius2); - bloom_sum += SampleLocation(pos + float2(-1.5, 1.5) * radius2); - bloom_sum += SampleLocation(pos + float2(0.0, 2.5) * radius2); - bloom_sum += SampleLocation(pos + float2(1.5, 1.5) * radius2); - bloom_sum += SampleLocation(pos + float2(2.5, 0.0) * radius2); - bloom_sum += SampleLocation(pos + float2(1.5, -1.5) * radius2); - bloom_sum += SampleLocation(pos + float2(0.0, -2.5) * radius2); - - bloom_sum *= 0.07; - bloom_sum -= float4(0.3, 0.3, 0.3, 0.3); - bloom_sum = max(bloom_sum, float4(0.0, 0.0, 0.0, 0.0)); - - float2 vpos = (GetCoordinates() - float2(0.5, 0.5)) * 2.0; - float dist = (dot(vpos, vpos)); - dist = 1.0 - 0.4*dist; - - SetOutput((c_center * 0.7 + bloom_sum) * dist); -} diff --git a/Data/Sys/Shaders/brighten.glsl b/Data/Sys/Shaders/brighten.glsl deleted file mode 100644 index b1cdf2480604..000000000000 --- a/Data/Sys/Shaders/brighten.glsl +++ /dev/null @@ -1,4 +0,0 @@ -void main() -{ - SetOutput(Sample()* 3.0); -} diff --git a/Data/Sys/Shaders/darkerbrighter.glsl b/Data/Sys/Shaders/darkerbrighter.glsl deleted file mode 100644 index 99b1f4073c7d..000000000000 --- a/Data/Sys/Shaders/darkerbrighter.glsl +++ /dev/null @@ -1,33 +0,0 @@ -void main() -{ - float4 c0 = Sample(); - float4 c1 = SampleOffset(int2(-1, 0)); - float4 c2 = SampleOffset(int2( 0, -1)); - float4 c3 = SampleOffset(int2( 1, 0)); - float4 c4 = SampleOffset(int2( 0, 1)); - - float red = c0.r; - float blue = c0.b; - float green = c0.g; - - float red2 = (c1.r + c2.r + c3.r + c4.r) / 4.0; - float blue2 = (c1.b + c2.b + c3.b + c4.b) / 4.0; - float green2 = (c1.g + c2.g + c3.g + c4.g) / 4.0; - - if (red2 > 0.3) - red = c0.r + c0.r / 2.0; - else - red = c0.r - c0.r / 2.0; - - if (green2 > 0.3) - green = c0.g+ c0.g / 2.0; - else - green = c0.g - c0.g / 2.0; - - if (blue2 > 0.3) - blue = c0.b+ c0.b / 2.0; - else - blue = c0.b - c0.b / 2.0; - - SetOutput(float4(red, green, blue, c0.a)); -} diff --git a/Data/Sys/Shaders/invertedoutline.glsl b/Data/Sys/Shaders/invertedoutline.glsl deleted file mode 100644 index fa1cb0f9bd73..000000000000 --- a/Data/Sys/Shaders/invertedoutline.glsl +++ /dev/null @@ -1,7 +0,0 @@ -void main() -{ - float4 c0 = Sample(); - float4 c1 = SampleOffset(int2(5, 5)); - - SetOutput(c0 - c1); -} diff --git a/Data/Sys/Shaders/lens_distortion.glsl b/Data/Sys/Shaders/lens_distortion.glsl deleted file mode 100644 index f36f2325244c..000000000000 --- a/Data/Sys/Shaders/lens_distortion.glsl +++ /dev/null @@ -1,93 +0,0 @@ -/* -[configuration] - -[OptionRangeFloat] -GUIName = Distortion amount -OptionName = DISTORTION_FACTOR -MinValue = 1.0 -MaxValue = 10.0 -StepAmount = 0.5 -DefaultValue = 4.0 - -[OptionRangeFloat] -GUIName = Eye Distance Offset -OptionName = EYE_OFFSET -MinValue = 0.0 -MaxValue = 10.0 -StepAmount = 0.25 -DefaultValue = 5.0 - -[OptionRangeFloat] -GUIName = Zoom adjustment -OptionName = SIZE_ADJUST -MinValue = 0.0 -MaxValue = 1.0 -StepAmount = 0.025 -DefaultValue = 0.5 - -[OptionRangeFloat] -GUIName = Aspect Ratio adjustment -OptionName = ASPECT_ADJUST -MinValue = 0.0 -MaxValue = 1.0 -StepAmount = 0.025 -DefaultValue = 0.5 - -[/configuration] -*/ - - -void main() -{ - // Base Cardboard distortion parameters - float factor = GetOption(DISTORTION_FACTOR) * 0.01f; - float ka = factor * 3.0f; - float kb = factor * 5.0f; - - // size and aspect adjustment - float sizeAdjust = 1.0f - GetOption(SIZE_ADJUST) + 0.5f; - float aspectAdjustment = 1.25f - GetOption(ASPECT_ADJUST); - - // offset centering per eye - float stereoOffset = GetOption(EYE_OFFSET) * 0.01f; - float offsetAdd; - - // layer0 = left eye, layer1 = right eye - if (src_layer == 1) - { - offsetAdd = stereoOffset; - } - else - { - offsetAdd = 0.0 - stereoOffset; - } - - // convert coordinates to NDC space - float2 fragPos = (GetCoordinates() - 0.5f - float2(offsetAdd, 0.0f)) * 2.0f; - - // Calculate the source location "radius" (distance from the centre of the viewport) - float destR = length(fragPos); - - // find the radius multiplier - float srcR = destR * sizeAdjust + ( ka * pow(destR, 2.0) + kb * pow(destR, 4.0)); - - // Calculate the source vector (radial) - float2 correctedRadial = normalize(fragPos) * srcR; - - // fix aspect ratio - float2 widenedRadial = correctedRadial * float2(aspectAdjustment, 1.0f); - - // Transform the coordinates (from [-1,1]^2 to [0, 1]^2) - float2 uv = (widenedRadial/2.0f) + float2(0.5f, 0.5f) + float2(offsetAdd, 0.0f); - - // Sample the texture at the source location - if(any(clamp(uv, 0.0, 1.0) != uv)) - { - // black if beyond bounds - SetOutput(float4(0.0, 0.0, 0.0, 0.0)); - } - else - { - SetOutput(SampleLocation(uv)); - } -} diff --git a/Data/Sys/Shaders/posterize.glsl b/Data/Sys/Shaders/posterize.glsl deleted file mode 100644 index dba7e59ee368..000000000000 --- a/Data/Sys/Shaders/posterize.glsl +++ /dev/null @@ -1,18 +0,0 @@ -void main() -{ - float4 c0 = Sample(); - float red = 0.0; - float green = 0.0; - float blue = 0.0; - - if (c0.r > 0.25) - red = c0.r; - - if (c0.g > 0.25) - green = c0.g; - - if (c0.b > 0.25) - blue = c0.b; - - SetOutput(float4(red, green, blue, 1.0)); -} diff --git a/Data/Sys/Shaders/posterize2.glsl b/Data/Sys/Shaders/posterize2.glsl deleted file mode 100644 index 2000b0f72681..000000000000 --- a/Data/Sys/Shaders/posterize2.glsl +++ /dev/null @@ -1,18 +0,0 @@ -float bound(float color) -{ - if (color < 0.35) - { - if (color < 0.25) - return color; - - return 0.5; - } - - return 1.0; -} - -void main() -{ - float4 c0 = Sample(); - SetOutput(float4(bound(c0.r), bound(c0.g), bound(c0.b), c0.a)); -} diff --git a/Data/Sys/Shaders/swap_RGB_GBR.glsl b/Data/Sys/Shaders/swap_RGB_GBR.glsl deleted file mode 100644 index 410958d2a5d5..000000000000 --- a/Data/Sys/Shaders/swap_RGB_GBR.glsl +++ /dev/null @@ -1,4 +0,0 @@ -void main() -{ - SetOutput(Sample().gbra); -} diff --git a/Data/Sys/Shaders/swap_RGB_GRB.glsl b/Data/Sys/Shaders/swap_RGB_GRB.glsl deleted file mode 100644 index 7a51768421ff..000000000000 --- a/Data/Sys/Shaders/swap_RGB_GRB.glsl +++ /dev/null @@ -1,4 +0,0 @@ -void main() -{ - SetOutput(Sample().grba); -} diff --git a/Source/Core/Common/FileUtil.h b/Source/Core/Common/FileUtil.h index e2021bdab4c1..59897140c463 100644 --- a/Source/Core/Common/FileUtil.h +++ b/Source/Core/Common/FileUtil.h @@ -12,13 +12,9 @@ #include #include "Common/CommonTypes.h" - -#ifdef _WIN32 #include "Common/StringUtil.h" -#endif #ifdef ANDROID -#include "Common/StringUtil.h" #include "jni/AndroidCommon/AndroidCommon.h" #endif diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index 6b8384283981..c94ae023624e 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -620,6 +620,8 @@ + + @@ -632,10 +634,28 @@ + + + + + + + + + + + + + + + + + + + - @@ -1183,6 +1203,8 @@ + + @@ -1191,11 +1213,25 @@ + + + + + + + + + + + + + + + - diff --git a/Source/Core/DolphinQt/CMakeLists.txt b/Source/Core/DolphinQt/CMakeLists.txt index 1c8a41d606d4..b779e1fbca57 100644 --- a/Source/Core/DolphinQt/CMakeLists.txt +++ b/Source/Core/DolphinQt/CMakeLists.txt @@ -95,6 +95,8 @@ add_executable(dolphin-emu Config/Graphics/AdvancedWidget.h Config/Graphics/BalloonTip.cpp Config/Graphics/BalloonTip.h + Config/Graphics/CustomShaderWindow.cpp + Config/Graphics/CustomShaderWindow.h Config/Graphics/EnhancementsWidget.cpp Config/Graphics/EnhancementsWidget.h Config/Graphics/GeneralWidget.cpp @@ -103,6 +105,10 @@ add_executable(dolphin-emu Config/Graphics/GraphicsBool.h Config/Graphics/GraphicsChoice.cpp Config/Graphics/GraphicsChoice.h + Config/Graphics/GraphicsColor.cpp + Config/Graphics/GraphicsColor.h + Config/Graphics/GraphicsImage.cpp + Config/Graphics/GraphicsImage.h Config/Graphics/GraphicsInteger.cpp Config/Graphics/GraphicsInteger.h Config/Graphics/GraphicsRadio.cpp @@ -114,10 +120,28 @@ add_executable(dolphin-emu Config/Graphics/GraphicsWindow.h Config/Graphics/HacksWidget.cpp Config/Graphics/HacksWidget.h - Config/Graphics/PostProcessingConfigWindow.cpp - Config/Graphics/PostProcessingConfigWindow.h + Config/Graphics/PostProcessingAddShaderDialog.cpp + Config/Graphics/PostProcessingAddShaderDialog.h + Config/Graphics/PostProcessingAddShaderListWidget.cpp + Config/Graphics/PostProcessingAddShaderListWidget.h + Config/Graphics/PostProcessingShaderListWidget.cpp + Config/Graphics/PostProcessingShaderListWidget.h Config/Graphics/SoftwareRendererWidget.cpp Config/Graphics/SoftwareRendererWidget.h + Config/Graphics/Trigger/GraphicsTriggerSelectionDialog.cpp + Config/Graphics/Trigger/GraphicsTriggerSelectionDialog.h + Config/Graphics/Trigger/GraphicsTriggerWidget.cpp + Config/Graphics/Trigger/GraphicsTriggerWidget.h + Config/Graphics/Trigger/GraphicsTriggerWindow.cpp + Config/Graphics/Trigger/GraphicsTriggerWindow.h + Config/Graphics/Trigger/NewDrawCall2DTriggerDialog.cpp + Config/Graphics/Trigger/NewDrawCall2DTriggerDialog.h + Config/Graphics/Trigger/NewDrawCall3DTriggerDialog.cpp + Config/Graphics/Trigger/NewDrawCall3DTriggerDialog.h + Config/Graphics/Trigger/NewEFBTriggerDialog.cpp + Config/Graphics/Trigger/NewEFBTriggerDialog.h + Config/Graphics/Trigger/NewTextureLoadTriggerDialog.cpp + Config/Graphics/Trigger/NewTextureLoadTriggerDialog.h Config/InfoWidget.cpp Config/InfoWidget.h Config/LogConfigWidget.cpp @@ -195,6 +219,10 @@ add_executable(dolphin-emu Config/ToolTipControls/ToolTipCheckBox.h Config/ToolTipControls/ToolTipComboBox.cpp Config/ToolTipControls/ToolTipComboBox.h + Config/ToolTipControls/ToolTipLineEdit.cpp + Config/ToolTipControls/ToolTipLineEdit.h + Config/ToolTipControls/ToolTipPushButton.cpp + Config/ToolTipControls/ToolTipPushButton.h Config/ToolTipControls/ToolTipRadioButton.cpp Config/ToolTipControls/ToolTipRadioButton.h Config/ToolTipControls/ToolTipSlider.cpp @@ -541,12 +569,12 @@ if(APPLE) add_custom_command(TARGET dolphin-emu POST_BUILD COMMAND /usr/bin/codesign -f -s "${MACOS_CODE_SIGNING_IDENTITY}" --deep --options=runtime --entitlements "${CMAKE_SOURCE_DIR}/Source/Core/DolphinQt/DolphinEmu$<$:Debug>.entitlements" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Dolphin.app" || true) - + # Code sign builds for build systems that do have release/debug variants (Xcode) add_custom_command(TARGET dolphin-emu POST_BUILD COMMAND /usr/bin/codesign -f -s "${MACOS_CODE_SIGNING_IDENTITY}" --deep --options=runtime --entitlements "${CMAKE_SOURCE_DIR}/Source/Core/DolphinQt/DolphinEmuDebug.entitlements" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG}/Dolphin.app" || true) - + add_custom_command(TARGET dolphin-emu POST_BUILD COMMAND /usr/bin/codesign -f -s "${MACOS_CODE_SIGNING_IDENTITY}" --deep --options=runtime --entitlements "${CMAKE_SOURCE_DIR}/Source/Core/DolphinQt/DolphinEmu.entitlements" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE}/Dolphin.app" || true) diff --git a/Source/Core/DolphinQt/Config/Graphics/AdvancedWidget.cpp b/Source/Core/DolphinQt/Config/Graphics/AdvancedWidget.cpp index 3a62620f1c9e..f273e4553e26 100644 --- a/Source/Core/DolphinQt/Config/Graphics/AdvancedWidget.cpp +++ b/Source/Core/DolphinQt/Config/Graphics/AdvancedWidget.cpp @@ -19,7 +19,9 @@ #include "DolphinQt/Config/Graphics/GraphicsChoice.h" #include "DolphinQt/Config/Graphics/GraphicsInteger.h" #include "DolphinQt/Config/Graphics/GraphicsWindow.h" +#include "DolphinQt/Config/Graphics/Trigger/GraphicsTriggerWindow.h" #include "DolphinQt/Config/ToolTipControls/ToolTipCheckBox.h" +#include "DolphinQt/Config/ToolTipControls/ToolTipPushButton.h" #include "DolphinQt/Settings.h" #include "VideoCommon/VideoConfig.h" @@ -120,6 +122,7 @@ void AdvancedWidget::CreateWidgets() m_enable_prog_scan = new ToolTipCheckBox(tr("Enable Progressive Scan")); m_backend_multithreading = new GraphicsBool(tr("Backend Multithreading"), Config::GFX_BACKEND_MULTITHREADING); + m_configure_graphics_triggers = new ToolTipPushButton(tr("Configure")); misc_layout->addWidget(m_enable_cropping, 0, 0); misc_layout->addWidget(m_enable_prog_scan, 0, 1); @@ -130,6 +133,8 @@ void AdvancedWidget::CreateWidgets() misc_layout->addWidget(m_borderless_fullscreen, 1, 1); #endif + dump_layout->addWidget(new QLabel(tr("Graphics Triggers:")), 2, 0); + dump_layout->addWidget(m_configure_graphics_triggers, 2, 1); // Experimental. auto* experimental_box = new QGroupBox(tr("Experimental")); @@ -158,6 +163,10 @@ void AdvancedWidget::ConnectWidgets() connect(m_dump_use_ffv1, &QCheckBox::toggled, this, &AdvancedWidget::SaveSettings); connect(m_enable_prog_scan, &QCheckBox::toggled, this, &AdvancedWidget::SaveSettings); connect(m_dump_textures, &QCheckBox::toggled, this, &AdvancedWidget::SaveSettings); + connect(m_configure_graphics_triggers, &QPushButton::pressed, this, [this]() { + GraphicsTriggerWindow trigger_window(this); + trigger_window.exec(); + }); } void AdvancedWidget::LoadSettings() @@ -276,6 +285,11 @@ void AdvancedWidget::AddDescriptions() "unchecked."); #endif + static const char TR_GRAPHICS_TRIGGER_CONFIGURE_DESCRIPTION[] = QT_TR_NOOP( + "Launches a window to configure when enhanced graphical effects can be triggered. " + "

If unsure, leave this " + "unchecked."); + m_enable_wireframe->SetDescription(tr(TR_WIREFRAME_DESCRIPTION)); m_show_statistics->SetDescription(tr(TR_SHOW_STATS_DESCRIPTION)); m_enable_format_overlay->SetDescription(tr(TR_TEXTURE_FORMAT_DESCRIPTION)); @@ -298,5 +312,7 @@ void AdvancedWidget::AddDescriptions() #ifdef _WIN32 m_borderless_fullscreen->SetDescription(tr(TR_BORDERLESS_FULLSCREEN_DESCRIPTION)); #endif + m_configure_graphics_triggers->SetDescription(tr(TR_GRAPHICS_TRIGGER_CONFIGURE_DESCRIPTION)); + m_configure_graphics_triggers->SetTitle(tr("Configure Graphics Triggers")); m_defer_efb_access_invalidation->SetDescription(tr(TR_DEFER_EFB_ACCESS_INVALIDATION_DESCRIPTION)); } diff --git a/Source/Core/DolphinQt/Config/Graphics/AdvancedWidget.h b/Source/Core/DolphinQt/Config/Graphics/AdvancedWidget.h index abda8395f741..7a9b6da288a5 100644 --- a/Source/Core/DolphinQt/Config/Graphics/AdvancedWidget.h +++ b/Source/Core/DolphinQt/Config/Graphics/AdvancedWidget.h @@ -13,6 +13,7 @@ class QCheckBox; class QComboBox; class QSpinBox; class ToolTipCheckBox; +class ToolTipPushButton; class AdvancedWidget final : public GraphicsWidget { @@ -58,6 +59,7 @@ class AdvancedWidget final : public GraphicsWidget ToolTipCheckBox* m_enable_prog_scan; GraphicsBool* m_backend_multithreading; GraphicsBool* m_borderless_fullscreen; + ToolTipPushButton* m_configure_graphics_triggers; // Experimental GraphicsBool* m_defer_efb_access_invalidation; diff --git a/Source/Core/DolphinQt/Config/Graphics/CustomShaderWindow.cpp b/Source/Core/DolphinQt/Config/Graphics/CustomShaderWindow.cpp new file mode 100644 index 000000000000..fbc799cc4202 --- /dev/null +++ b/Source/Core/DolphinQt/Config/Graphics/CustomShaderWindow.cpp @@ -0,0 +1,298 @@ +// Copyright 2020 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "DolphinQt/Config/Graphics/CustomShaderWindow.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Common/FileSearch.h" +#include "Common/FileUtil.h" + +#include "DolphinQt/Config/Graphics/GraphicsWindow.h" +#include "DolphinQt/Config/Graphics/PostProcessingShaderListWidget.h" +#include "DolphinQt/Config/Graphics/Trigger/GraphicsTriggerSelectionDialog.h" +#include "DolphinQt/QtUtils/ModalMessageBox.h" +#include "DolphinQt/Resources.h" +#include "DolphinQt/Settings.h" +#include "VideoCommon/VideoConfig.h" + +constexpr const char* CUSTOM_SHADERS_DIR = "CustomShaders/"; +constexpr const char* PROFILES_DIR = "Profiles/"; + +CustomShaderWindow::CustomShaderWindow(QWidget* parent) : QWidget(parent) +{ + setWindowTitle(tr("Shader Configuration")); + setWindowIcon(Resources::GetAppIcon()); + + CreateTriggersLayout(); + CreateProfilesLayout(); + + OnTriggerPointChanged(); + CreateMainLayout(); + ConnectWidgets(); + + PopulateProfileSelection(); + + installEventFilter(this); +} + +void CustomShaderWindow::CreateProfilesLayout() +{ + m_profiles_layout = new QHBoxLayout(); + m_profiles_box = new QGroupBox(tr("Profile")); + m_profiles_combo = new QComboBox(); + m_profiles_load = new QPushButton(tr("Load")); + m_profiles_save = new QPushButton(tr("Save")); + m_profiles_delete = new QPushButton(tr("Delete")); + + auto* button_layout = new QHBoxLayout(); + + m_profiles_combo->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); + m_profiles_combo->setMinimumWidth(100); + m_profiles_combo->setEditable(true); + + m_profiles_layout->addWidget(m_profiles_combo); + button_layout->addWidget(m_profiles_load); + button_layout->addWidget(m_profiles_save); + button_layout->addWidget(m_profiles_delete); + m_profiles_layout->addLayout(button_layout); + + m_profiles_box->setLayout(m_profiles_layout); +} + +void CustomShaderWindow::CreateTriggersLayout() +{ + m_triggers_layout = new QHBoxLayout(); + m_triggers_box = new QGroupBox(tr("Triggers")); + m_selected_trigger_name = + new QLabel(QString::fromStdString(g_Config.m_trigger_config.m_chosen_trigger_point)); + m_select_trigger = new QPushButton(tr("Select...")); + + m_triggers_layout->addWidget(m_selected_trigger_name); + m_triggers_layout->addWidget(m_select_trigger); + + m_triggers_box->setLayout(m_triggers_layout); +} + +void CustomShaderWindow::CreateMainLayout() +{ + m_main_layout = new QVBoxLayout(); + m_config_layout = new QHBoxLayout(); + m_button_box = new QDialogButtonBox(QDialogButtonBox::Close); + m_config_layout->addWidget(m_triggers_box); + m_config_layout->addWidget(m_profiles_box); + + m_main_layout->addLayout(m_config_layout); + m_main_layout->addWidget(m_pp_list_widget); + m_main_layout->addWidget(m_button_box); + + setLayout(m_main_layout); +} + +void CustomShaderWindow::ConnectWidgets() +{ + connect(m_select_trigger, &QPushButton::clicked, this, + &CustomShaderWindow::OnSelectTriggerPressed); + + connect(m_profiles_save, &QPushButton::clicked, this, &CustomShaderWindow::OnSaveProfilePressed); + connect(m_profiles_load, &QPushButton::clicked, this, &CustomShaderWindow::OnLoadProfilePressed); + connect(m_profiles_delete, &QPushButton::clicked, this, + &CustomShaderWindow::OnDeleteProfilePressed); + + connect(m_profiles_combo, qOverload(&QComboBox::currentIndexChanged), this, + &CustomShaderWindow::OnSelectProfile); + connect(m_profiles_combo, &QComboBox::editTextChanged, this, + &CustomShaderWindow::OnProfileTextChanged); + + // We currently use the "Close" button as an "Accept" button + connect(m_button_box, &QDialogButtonBox::rejected, this, &QDialog::hide); +} + +void CustomShaderWindow::UpdateProfileIndex() +{ + // Make sure currentIndex and currentData are accurate when the user manually types a name. + + const auto current_text = m_profiles_combo->currentText(); + const int text_index = m_profiles_combo->findText(current_text); + m_profiles_combo->setCurrentIndex(text_index); + + if (text_index == -1) + m_profiles_combo->setCurrentText(current_text); +} + +void CustomShaderWindow::UpdateProfileButtonState() +{ + // Make sure save/delete buttons are disabled for built-in profiles + + bool builtin = false; + if (m_profiles_combo->findText(m_profiles_combo->currentText()) != -1) + { + const QString profile_path = m_profiles_combo->currentData().toString(); + builtin = profile_path.startsWith(QString::fromStdString(File::GetSysDirectory())); + } + + m_profiles_save->setEnabled(!builtin); + m_profiles_delete->setEnabled(!builtin); +} + +void CustomShaderWindow::OnSelectProfile(int) +{ + UpdateProfileButtonState(); +} + +void CustomShaderWindow::OnProfileTextChanged(const QString&) +{ + UpdateProfileButtonState(); +} + +void CustomShaderWindow::OnDeleteProfilePressed() +{ + UpdateProfileIndex(); + + const QString profile_name = m_profiles_combo->currentText(); + const QString profile_path = m_profiles_combo->currentData().toString(); + + if (m_profiles_combo->currentIndex() == -1 || !File::Exists(profile_path.toStdString())) + { + ModalMessageBox error(this); + error.setIcon(QMessageBox::Critical); + error.setWindowTitle(tr("Error")); + error.setText(tr("The profile '%1' does not exist").arg(profile_name)); + error.exec(); + return; + } + + ModalMessageBox confirm(this); + + confirm.setIcon(QMessageBox::Warning); + confirm.setWindowTitle(tr("Confirm")); + confirm.setText(tr("Are you sure that you want to delete '%1'?").arg(profile_name)); + confirm.setInformativeText(tr("This cannot be undone!")); + confirm.setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel); + + if (confirm.exec() != QMessageBox::Yes) + { + return; + } + + m_profiles_combo->removeItem(m_profiles_combo->currentIndex()); + m_profiles_combo->setCurrentIndex(-1); + + File::Delete(profile_path.toStdString()); + + ModalMessageBox result(this); + result.setIcon(QMessageBox::Information); + result.setWindowModality(Qt::WindowModal); + result.setWindowTitle(tr("Success")); + result.setText(tr("Successfully deleted '%1'.").arg(profile_name)); +} + +void CustomShaderWindow::OnLoadProfilePressed() +{ + UpdateProfileIndex(); + + if (m_profiles_combo->currentIndex() == -1) + { + ModalMessageBox error(this); + error.setIcon(QMessageBox::Critical); + error.setWindowTitle(tr("Error")); + error.setText(tr("The profile '%1' does not exist").arg(m_profiles_combo->currentText())); + error.exec(); + return; + } + + const QString profile_path = m_profiles_combo->currentData().toString(); + // g_Config.LoadCustomShaderPreset(profile_path.toStdString()); +} + +void CustomShaderWindow::OnSaveProfilePressed() +{ + const QString profile_name = m_profiles_combo->currentText(); + + if (profile_name.isEmpty()) + return; + + const std::string profile_path = File::GetUserPath(D_CONFIG_IDX) + PROFILES_DIR + + CUSTOM_SHADERS_DIR + profile_name.toStdString() + ".ini"; + + File::CreateFullPath(profile_path); + + // g_Config.SaveCustomShaderPreset(profile_path); + + if (m_profiles_combo->findText(profile_name) == -1) + { + PopulateProfileSelection(); + m_profiles_combo->setCurrentIndex(m_profiles_combo->findText(profile_name)); + } +} + +void CustomShaderWindow::PopulateProfileSelection() +{ + m_profiles_combo->clear(); + + const std::string profiles_path = + File::GetUserPath(D_CONFIG_IDX) + PROFILES_DIR + CUSTOM_SHADERS_DIR; + for (const auto& filename : Common::DoFileSearch({profiles_path}, {".ini"})) + { + std::string basename; + SplitPath(filename, nullptr, &basename, nullptr); + m_profiles_combo->addItem(QString::fromStdString(basename), QString::fromStdString(filename)); + } + + m_profiles_combo->setCurrentIndex(-1); +} + +void CustomShaderWindow::OnSelectTriggerPressed() +{ + GraphicsTriggerSelectionDialog dialog(this); + if (dialog.exec() == QDialog::Accepted) + { + g_Config.m_trigger_config.m_chosen_trigger_point = dialog.GetTriggerName().toStdString(); + m_selected_trigger_name->setText(dialog.GetTriggerName()); + OnTriggerPointChanged(); + g_Config.m_trigger_config.m_changes++; + } +} + +void CustomShaderWindow::OnTriggerPointChanged() +{ + auto& shader_group_config = + g_Config.m_trigger_config + .m_trigger_name_to_shader_groups[g_Config.m_trigger_config.m_chosen_trigger_point]; + auto val = new PostProcessingShaderListWidget(this, &shader_group_config); + + if (m_pp_list_widget == nullptr) + { + m_pp_list_widget = val; + } + else + { + m_main_layout->replaceWidget(m_pp_list_widget, val); + auto old = m_pp_list_widget; + m_pp_list_widget = val; + old->deleteLater(); + } +} + +bool CustomShaderWindow::eventFilter(QObject* object, QEvent* event) +{ + // Close when escape is pressed + if (event->type() == QEvent::KeyPress) + { + if (static_cast(event)->matches(QKeySequence::Cancel)) + hide(); + } + + return false; +} diff --git a/Source/Core/DolphinQt/Config/Graphics/CustomShaderWindow.h b/Source/Core/DolphinQt/Config/Graphics/CustomShaderWindow.h new file mode 100644 index 000000000000..249d51bf5c45 --- /dev/null +++ b/Source/Core/DolphinQt/Config/Graphics/CustomShaderWindow.h @@ -0,0 +1,66 @@ +// Copyright 2020 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +class QComboBox; +class QGroupBox; +class QDialogButtonBox; +class QHBoxLayout; +class QLabel; +class QVBoxLayout; +class QPushButton; +class QWidget; +class PostProcessingShaderListWidget; + +class CustomShaderWindow final : public QWidget +{ + Q_OBJECT +public: + explicit CustomShaderWindow(QWidget* parent = nullptr); + +private: + void CreateProfilesLayout(); + void CreateTriggersLayout(); + void CreateMainLayout(); + void ConnectWidgets(); + + void OnSelectProfile(int index); + void OnProfileTextChanged(const QString& text); + void OnDeleteProfilePressed(); + void OnLoadProfilePressed(); + void OnSaveProfilePressed(); + void UpdateProfileIndex(); + void UpdateProfileButtonState(); + void PopulateProfileSelection(); + + void OnSelectTriggerPressed(); + void OnTriggerPointChanged(); + + bool eventFilter(QObject* object, QEvent* event) override; + + // Main + QVBoxLayout* m_main_layout; + QHBoxLayout* m_config_layout; + QDialogButtonBox* m_button_box; + + // Profiles + QGroupBox* m_profiles_box; + QHBoxLayout* m_profiles_layout; + QComboBox* m_profiles_combo; + QPushButton* m_profiles_load; + QPushButton* m_profiles_save; + QPushButton* m_profiles_delete; + + // Triggers + QGroupBox* m_triggers_box; + QHBoxLayout* m_triggers_layout; + QLabel* m_selected_trigger_name; + QPushButton* m_select_trigger; + + PostProcessingShaderListWidget* m_pp_list_widget = nullptr; +}; diff --git a/Source/Core/DolphinQt/Config/Graphics/EnhancementsWidget.cpp b/Source/Core/DolphinQt/Config/Graphics/EnhancementsWidget.cpp index 94021a9c16aa..caadb493f2fe 100644 --- a/Source/Core/DolphinQt/Config/Graphics/EnhancementsWidget.cpp +++ b/Source/Core/DolphinQt/Config/Graphics/EnhancementsWidget.cpp @@ -18,12 +18,12 @@ #include "DolphinQt/Config/Graphics/GraphicsChoice.h" #include "DolphinQt/Config/Graphics/GraphicsSlider.h" #include "DolphinQt/Config/Graphics/GraphicsWindow.h" -#include "DolphinQt/Config/Graphics/PostProcessingConfigWindow.h" +#include "DolphinQt/Config/Graphics/CustomShaderWindow.h" +#include "DolphinQt/Config/ToolTipControls/ToolTipPushButton.h" #include "DolphinQt/Settings.h" #include "UICommon/VideoUtils.h" -#include "VideoCommon/PostProcessing.h" #include "VideoCommon/VideoBackendBase.h" #include "VideoCommon/VideoCommon.h" #include "VideoCommon/VideoConfig.h" @@ -76,7 +76,6 @@ void EnhancementsWidget::CreateWidgets() Config::GFX_ENHANCE_MAX_ANISOTROPY); m_pp_effect = new ToolTipComboBox(); - m_configure_pp_effect = new QPushButton(tr("Configure")); m_scaled_efb_copy = new GraphicsBool(tr("Scaled EFB Copy"), Config::GFX_HACK_COPY_EFB_SCALED); m_per_pixel_lighting = new GraphicsBool(tr("Per-Pixel Lighting"), Config::GFX_ENABLE_PIXEL_LIGHTING); @@ -90,6 +89,7 @@ void EnhancementsWidget::CreateWidgets() new GraphicsBool(tr("Disable Copy Filter"), Config::GFX_ENHANCE_DISABLE_COPY_FILTER); m_arbitrary_mipmap_detection = new GraphicsBool(tr("Arbitrary Mipmap Detection"), Config::GFX_ENHANCE_ARBITRARY_MIPMAP_DETECTION); + m_pp_configure = new ToolTipPushButton(tr("Configure")); enhancements_layout->addWidget(new QLabel(tr("Internal Resolution:")), 0, 0); enhancements_layout->addWidget(m_ir_combo, 0, 1, 1, -1); @@ -98,9 +98,8 @@ void EnhancementsWidget::CreateWidgets() enhancements_layout->addWidget(new QLabel(tr("Anisotropic Filtering:")), 2, 0); enhancements_layout->addWidget(m_af_combo, 2, 1, 1, -1); - enhancements_layout->addWidget(new QLabel(tr("Post-Processing Effect:")), 4, 0); - enhancements_layout->addWidget(m_pp_effect, 4, 1); - enhancements_layout->addWidget(m_configure_pp_effect, 4, 2); + enhancements_layout->addWidget(new QLabel(tr("Post-Processing:")), 4, 0); + enhancements_layout->addWidget(m_pp_configure, 4, 1, 1, -1); enhancements_layout->addWidget(m_scaled_efb_copy, 5, 0); enhancements_layout->addWidget(m_per_pixel_lighting, 5, 1); @@ -143,73 +142,17 @@ void EnhancementsWidget::ConnectWidgets() { connect(m_aa_combo, qOverload(&QComboBox::currentIndexChanged), [this](int) { SaveSettings(); }); - connect(m_pp_effect, qOverload(&QComboBox::currentIndexChanged), - [this](int) { SaveSettings(); }); connect(m_3d_mode, qOverload(&QComboBox::currentIndexChanged), [this] { - m_block_save = true; - LoadPPShaders(); - m_block_save = false; - SaveSettings(); }); - connect(m_configure_pp_effect, &QPushButton::clicked, this, - &EnhancementsWidget::ConfigurePostProcessingShader); -} - -void EnhancementsWidget::LoadPPShaders() -{ - std::vector shaders = VideoCommon::PostProcessing::GetShaderList(); - if (g_Config.stereo_mode == StereoMode::Anaglyph) - { - shaders = VideoCommon::PostProcessing::GetAnaglyphShaderList(); - } - else if (g_Config.stereo_mode == StereoMode::Passive) - { - shaders = VideoCommon::PostProcessing::GetPassiveShaderList(); - } - - m_pp_effect->clear(); - - if (g_Config.stereo_mode != StereoMode::Anaglyph && g_Config.stereo_mode != StereoMode::Passive) - m_pp_effect->addItem(tr("(off)")); - - auto selected_shader = Config::Get(Config::GFX_ENHANCE_POST_SHADER); - - bool found = false; - - for (const auto& shader : shaders) - { - m_pp_effect->addItem(QString::fromStdString(shader)); - if (selected_shader == shader) - { - m_pp_effect->setCurrentIndex(m_pp_effect->count() - 1); - found = true; - } - } - - if (g_Config.stereo_mode == StereoMode::Anaglyph && !found) - m_pp_effect->setCurrentIndex(m_pp_effect->findText(QStringLiteral("dubois"))); - else if (g_Config.stereo_mode == StereoMode::Passive && !found) - m_pp_effect->setCurrentIndex(m_pp_effect->findText(QStringLiteral("horizontal"))); - - const bool supports_postprocessing = g_Config.backend_info.bSupportsPostProcessing; - m_pp_effect->setEnabled(supports_postprocessing); - - m_pp_effect->setToolTip(supports_postprocessing ? - QString{} : - tr("%1 doesn't support this feature.") - .arg(tr(g_video_backend->GetDisplayName().c_str()))); + connect(m_pp_configure, &QPushButton::pressed, this, [this]() { - VideoCommon::PostProcessingConfiguration pp_shader; - if (selected_shader != "(off)" && supports_postprocessing) - { - pp_shader.LoadShader(selected_shader); - m_configure_pp_effect->setEnabled(pp_shader.HasOptions()); - } - else - { - m_configure_pp_effect->setEnabled(false); - } + if (!m_shader_window) + m_shader_window = new CustomShaderWindow; + m_shader_window->show(); + m_shader_window->raise(); + m_shader_window->activateWindow(); + }); } void EnhancementsWidget::LoadSettings() @@ -228,9 +171,6 @@ void EnhancementsWidget::LoadSettings() QString::fromStdString(std::to_string(aa_selection) + "x " + (ssaa ? "SSAA" : "MSAA"))); m_aa_combo->setEnabled(m_aa_combo->count() > 1); - // Post Processing Shader - LoadPPShaders(); - // Stereoscopy const bool supports_stereoscopy = g_Config.backend_info.bSupportsGeometryShaders; m_3d_mode->setEnabled(supports_stereoscopy); @@ -263,24 +203,6 @@ void EnhancementsWidget::SaveSettings() Config::SetBaseOrCurrent(Config::GFX_SSAA, is_ssaa); - const bool anaglyph = g_Config.stereo_mode == StereoMode::Anaglyph; - const bool passive = g_Config.stereo_mode == StereoMode::Passive; - Config::SetBaseOrCurrent(Config::GFX_ENHANCE_POST_SHADER, - (!anaglyph && !passive && m_pp_effect->currentIndex() == 0) ? - "(off)" : - m_pp_effect->currentText().toStdString()); - - VideoCommon::PostProcessingConfiguration pp_shader; - if (Config::Get(Config::GFX_ENHANCE_POST_SHADER) != "(off)") - { - pp_shader.LoadShader(Config::Get(Config::GFX_ENHANCE_POST_SHADER)); - m_configure_pp_effect->setEnabled(pp_shader.HasOptions()); - } - else - { - m_configure_pp_effect->setEnabled(false); - } - LoadSettings(); } @@ -303,9 +225,6 @@ void EnhancementsWidget::AddDescriptions() "Enables anisotropic filtering, which enhances the visual quality of textures that " "are at oblique viewing angles.

Might cause issues in a small " "number of games.

If unsure, select 1x."); - static const char TR_POSTPROCESSING_DESCRIPTION[] = - QT_TR_NOOP("Applies a post-processing effect after rendering a frame.

If unsure, select (off)."); static const char TR_SCALED_EFB_COPY_DESCRIPTION[] = QT_TR_NOOP("Greatly increases the quality of textures generated using render-to-texture " "effects.

Slightly increases GPU load and causes relatively few graphical " @@ -380,9 +299,6 @@ void EnhancementsWidget::AddDescriptions() m_af_combo->SetTitle(tr("Anisotropic Filtering")); m_af_combo->SetDescription(tr(TR_ANISOTROPIC_FILTERING_DESCRIPTION)); - m_pp_effect->SetTitle(tr("Post-Processing Effect")); - m_pp_effect->SetDescription(tr(TR_POSTPROCESSING_DESCRIPTION)); - m_scaled_efb_copy->SetDescription(tr(TR_SCALED_EFB_COPY_DESCRIPTION)); m_per_pixel_lighting->SetDescription(tr(TR_PER_PIXEL_LIGHTING_DESCRIPTION)); @@ -410,9 +326,3 @@ void EnhancementsWidget::AddDescriptions() m_3d_swap_eyes->SetDescription(tr(TR_3D_SWAP_EYES_DESCRIPTION)); } - -void EnhancementsWidget::ConfigurePostProcessingShader() -{ - const std::string shader = Config::Get(Config::GFX_ENHANCE_POST_SHADER); - PostProcessingConfigWindow(this, shader).exec(); -} diff --git a/Source/Core/DolphinQt/Config/Graphics/EnhancementsWidget.h b/Source/Core/DolphinQt/Config/Graphics/EnhancementsWidget.h index 6054820df115..990d6a76e005 100644 --- a/Source/Core/DolphinQt/Config/Graphics/EnhancementsWidget.h +++ b/Source/Core/DolphinQt/Config/Graphics/EnhancementsWidget.h @@ -5,6 +5,7 @@ #include "DolphinQt/Config/Graphics/GraphicsWidget.h" +class CustomShaderWindow; class GraphicsBool; class GraphicsChoice; class GraphicsSlider; @@ -14,6 +15,7 @@ class QComboBox; class QPushButton; class QSlider; class ToolTipComboBox; +class ToolTipPushButton; class EnhancementsWidget final : public GraphicsWidget { @@ -28,15 +30,12 @@ class EnhancementsWidget final : public GraphicsWidget void CreateWidgets(); void ConnectWidgets(); void AddDescriptions(); - void ConfigurePostProcessingShader(); - void LoadPPShaders(); // Enhancements GraphicsChoice* m_ir_combo; ToolTipComboBox* m_aa_combo; GraphicsChoice* m_af_combo; ToolTipComboBox* m_pp_effect; - QPushButton* m_configure_pp_effect; GraphicsBool* m_scaled_efb_copy; GraphicsBool* m_per_pixel_lighting; GraphicsBool* m_force_texture_filtering; @@ -45,6 +44,8 @@ class EnhancementsWidget final : public GraphicsWidget GraphicsBool* m_force_24bit_color; GraphicsBool* m_disable_copy_filter; GraphicsBool* m_arbitrary_mipmap_detection; + ToolTipPushButton* m_pp_configure; + CustomShaderWindow* m_shader_window = nullptr; // Stereoscopy GraphicsChoice* m_3d_mode; diff --git a/Source/Core/DolphinQt/Config/Graphics/GraphicsColor.cpp b/Source/Core/DolphinQt/Config/Graphics/GraphicsColor.cpp new file mode 100644 index 000000000000..81fe87feafc1 --- /dev/null +++ b/Source/Core/DolphinQt/Config/Graphics/GraphicsColor.cpp @@ -0,0 +1,50 @@ +// Copyright 2020 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "DolphinQt/Config/Graphics/GraphicsColor.h" + +#include +#include + +GraphicsColor::GraphicsColor(QWidget* parent, bool supports_alpha) + : QPushButton(parent), m_supports_alpha(supports_alpha) +{ + connect(this, SIGNAL(clicked()), this, SLOT(ChangeColor())); +} + +void GraphicsColor::UpdateColor() +{ + setStyleSheet(QStringLiteral("background-color: ") + m_color.name()); + emit ColorIsUpdated(); +} + +void GraphicsColor::ChangeColor() +{ + QColor newColor; + if (m_supports_alpha) + { + newColor = QColorDialog::getColor(m_color, parentWidget(), QStringLiteral("Pick a color"), + QColorDialog::ShowAlphaChannel); + } + else + { + newColor = QColorDialog::getColor(m_color, parentWidget(), QStringLiteral("Pick a color"), + QColorDialog::ShowAlphaChannel); + } + if (newColor != m_color) + { + SetColor(newColor); + } +} + +void GraphicsColor::SetColor(const QColor& color) +{ + m_color = color; + UpdateColor(); +} + +const QColor& GraphicsColor::GetColor() +{ + return m_color; +} diff --git a/Source/Core/DolphinQt/Config/Graphics/GraphicsColor.h b/Source/Core/DolphinQt/Config/Graphics/GraphicsColor.h new file mode 100644 index 000000000000..93d0ba832d79 --- /dev/null +++ b/Source/Core/DolphinQt/Config/Graphics/GraphicsColor.h @@ -0,0 +1,28 @@ +// Copyright 2020 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +class GraphicsColor : public QPushButton +{ + Q_OBJECT +public: + GraphicsColor(QWidget* parent, bool supports_alpha); + + void SetColor(const QColor& color); + const QColor& GetColor(); + +signals: + void ColorIsUpdated(); +public slots: + void UpdateColor(); + void ChangeColor(); + +private: + QColor m_color; + bool m_supports_alpha = false; +}; diff --git a/Source/Core/DolphinQt/Config/Graphics/GraphicsImage.cpp b/Source/Core/DolphinQt/Config/Graphics/GraphicsImage.cpp new file mode 100644 index 000000000000..5e471daa6537 --- /dev/null +++ b/Source/Core/DolphinQt/Config/Graphics/GraphicsImage.cpp @@ -0,0 +1,76 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "DolphinQt/Config/Graphics/GraphicsImage.h" + +#include "DolphinQt/QtUtils/ModalMessageBox.h" + +#include +#include +#include +#include +#include + +GraphicsImage::GraphicsImage(QWidget* parent) + : QPushButton(QStringLiteral("Select image..."), parent) +{ + connect(this, SIGNAL(clicked()), this, SLOT(SelectImage())); + setAcceptDrops(true); +} + +void GraphicsImage::UpdateImage() +{ + setText(m_file_info.fileName()); + emit PathIsUpdated(); +} + +void GraphicsImage::SelectImage() +{ + QString path = + QFileDialog::getOpenFileName(this, tr("Select image"), QString(), tr("PNG files (*.png)")); + + if (path.isEmpty()) + return; + + m_file_info = QFileInfo(path); + UpdateImage(); +} + +void GraphicsImage::dragEnterEvent(QDragEnterEvent* event) +{ + if (event->mimeData()->hasUrls() && event->mimeData()->urls().size() == 1) + event->acceptProposedAction(); +} + +void GraphicsImage::dropEvent(QDropEvent* event) +{ + const auto& urls = event->mimeData()->urls(); + if (urls.empty()) + return; + + const auto& url = urls[0]; + QFileInfo file_info(url.toLocalFile()); + + if (!file_info.exists() || !file_info.isReadable()) + { + ModalMessageBox::critical(this, tr("Error"), + tr("Failed to open '%1'").arg(file_info.filePath())); + return; + } + + if (!file_info.isFile()) + { + ModalMessageBox::critical(this, tr("Error"), + tr("Not a file '%1'").arg(file_info.filePath())); + return; + } + + if (file_info.completeSuffix().toLower() != QStringLiteral("png")) + { + ModalMessageBox::critical(this, tr("Error"), tr("Not a png file '%1'").arg(file_info.filePath())); + return; + } + m_file_info = file_info; + UpdateImage(); +} diff --git a/Source/Core/DolphinQt/Config/Graphics/GraphicsImage.h b/Source/Core/DolphinQt/Config/Graphics/GraphicsImage.h new file mode 100644 index 000000000000..db3e3839b3e3 --- /dev/null +++ b/Source/Core/DolphinQt/Config/Graphics/GraphicsImage.h @@ -0,0 +1,28 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +class GraphicsImage final : public QPushButton +{ + Q_OBJECT +public: + explicit GraphicsImage(QWidget* parent = nullptr); + + std::string GetPath() const { return m_file_info.filePath().toStdString(); } + +signals: + void PathIsUpdated(); +public slots: + void UpdateImage(); + void SelectImage(); + +private: + void dragEnterEvent(QDragEnterEvent* event) override; + void dropEvent(QDropEvent* event) override; + QFileInfo m_file_info; +}; diff --git a/Source/Core/DolphinQt/Config/Graphics/PostProcessingAddShaderDialog.cpp b/Source/Core/DolphinQt/Config/Graphics/PostProcessingAddShaderDialog.cpp new file mode 100644 index 000000000000..deb138534eff --- /dev/null +++ b/Source/Core/DolphinQt/Config/Graphics/PostProcessingAddShaderDialog.cpp @@ -0,0 +1,72 @@ +// Copyright 2020 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "DolphinQt/Config/Graphics/PostProcessingAddShaderDialog.h" + +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include "Common/CommonFuncs.h" +#include "Common/CommonPaths.h" +#include "Common/FileSearch.h" +#include "Common/FileUtil.h" + +#include "DolphinQt/Config/Graphics/PostProcessingAddShaderListWidget.h" +#include "VideoCommon/PEShaderSystem/Constants.h" + +PostProcessingAddShaderDialog::PostProcessingAddShaderDialog(QWidget* parent) : QDialog(parent) +{ + CreateWidgets(); + setLayout(m_main_layout); +} + +void PostProcessingAddShaderDialog::CreateWidgets() +{ + setWindowTitle(tr("Add New Shader")); + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + + m_main_layout = new QVBoxLayout; + + const auto sys_shader_dir = QString::fromStdString( + fmt::format("{}/{}", File::GetSysDirectory(), + VideoCommon::PE::Constants::dolphin_shipped_public_shader_directory)); + const auto user_shader_dir = QString::fromStdString(File::GetUserPath(D_SHADERS_IDX)); + + m_system_shader_list = new PostProcessingAddShaderListWidget(this, sys_shader_dir, false); + m_user_shader_list = new PostProcessingAddShaderListWidget(this, user_shader_dir, true); + + m_shader_tabs = new QTabWidget; + m_shader_tabs->addTab(m_system_shader_list, tr("System")); + m_shader_tabs->addTab(m_user_shader_list, tr("User")); + m_main_layout->addWidget(m_shader_tabs); + + m_buttonbox = new QDialogButtonBox(); + auto* add_button = new QPushButton(tr("Add")); + auto* cancel_button = new QPushButton(tr("Cancel")); + m_buttonbox->addButton(add_button, QDialogButtonBox::AcceptRole); + m_buttonbox->addButton(cancel_button, QDialogButtonBox::RejectRole); + connect(add_button, &QPushButton::clicked, this, &PostProcessingAddShaderDialog::accept); + connect(cancel_button, &QPushButton::clicked, this, &PostProcessingAddShaderDialog::reject); + add_button->setDefault(true); + + m_main_layout->addWidget(m_buttonbox); +} + +std::vector PostProcessingAddShaderDialog::ChosenUserShaderPathes() const +{ + return m_user_shader_list->ChosenShaderPathes(); +} + +std::vector PostProcessingAddShaderDialog::ChosenSystemShaderPathes() const +{ + return m_system_shader_list->ChosenShaderPathes(); +} diff --git a/Source/Core/DolphinQt/Config/Graphics/PostProcessingAddShaderDialog.h b/Source/Core/DolphinQt/Config/Graphics/PostProcessingAddShaderDialog.h new file mode 100644 index 000000000000..915b1a6271d5 --- /dev/null +++ b/Source/Core/DolphinQt/Config/Graphics/PostProcessingAddShaderDialog.h @@ -0,0 +1,34 @@ +// Copyright 2020 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include + +#include +#include + +class PostProcessingAddShaderListWidget; +class QDialogButtonBox; +class QTabWidget; +class QVBoxLayout; + +class PostProcessingAddShaderDialog final : public QDialog +{ + Q_OBJECT +public: + explicit PostProcessingAddShaderDialog(QWidget* parent); + + std::vector ChosenUserShaderPathes() const; + std::vector ChosenSystemShaderPathes() const; + +private: + void CreateWidgets(); + + QDialogButtonBox* m_buttonbox; + QTabWidget* m_shader_tabs; + QVBoxLayout* m_main_layout; + PostProcessingAddShaderListWidget* m_system_shader_list; + PostProcessingAddShaderListWidget* m_user_shader_list; +}; diff --git a/Source/Core/DolphinQt/Config/Graphics/PostProcessingAddShaderListWidget.cpp b/Source/Core/DolphinQt/Config/Graphics/PostProcessingAddShaderListWidget.cpp new file mode 100644 index 000000000000..15ab225de70c --- /dev/null +++ b/Source/Core/DolphinQt/Config/Graphics/PostProcessingAddShaderListWidget.cpp @@ -0,0 +1,218 @@ +// Copyright 2020 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "DolphinQt/Config/Graphics/PostProcessingAddShaderListWidget.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Common/CommonFuncs.h" +#include "Common/CommonPaths.h" +#include "Common/FileSearch.h" +#include "Common/FileUtil.h" + +#include "DolphinQt/QtUtils/ModalMessageBox.h" + +PostProcessingAddShaderListWidget::PostProcessingAddShaderListWidget(QWidget* parent, + const QString& root_path, + bool allows_drag_drop) + : QWidget(parent), m_root_path(root_path), m_allows_drag_drop(allows_drag_drop) +{ + CreateWidgets(); + ConnectWidgets(); + setLayout(m_main_layout); + + if (m_allows_drag_drop) + { + setAcceptDrops(true); + } + + // Update with initial selection + OnShaderTypeChanged(); +} + +void PostProcessingAddShaderListWidget::CreateWidgets() +{ + m_main_layout = new QVBoxLayout; + + m_shader_type = new QComboBox(); + BuildShaderCategories(); + m_main_layout->addWidget(m_shader_type); + + m_search_text = new QLineEdit(); + m_search_text->setPlaceholderText(tr("Filter shaders...")); + m_main_layout->addWidget(m_search_text); + + m_shader_list = new QListWidget(); + m_shader_list->setSortingEnabled(false); + m_shader_list->setSelectionBehavior(QAbstractItemView::SelectionBehavior::SelectItems); + m_shader_list->setSelectionMode(QAbstractItemView::SelectionMode::SingleSelection); + m_shader_list->setSelectionRectVisible(true); + m_main_layout->addWidget(m_shader_list); +} + +void PostProcessingAddShaderListWidget::ConnectWidgets() +{ + connect(m_shader_list, &QListWidget::itemSelectionChanged, this, [this] { OnShadersSelected(); }); + connect(m_shader_type, &QComboBox::currentTextChanged, this, [this] { OnShaderTypeChanged(); }); + connect(m_search_text, &QLineEdit::textEdited, this, + [this](QString text) { OnSearchTextChanged(text); }); +} + +void PostProcessingAddShaderListWidget::BuildShaderCategories() +{ + m_shader_type->clear(); + for (QFileInfo info : m_root_path.entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot)) + { + m_shader_type->addItem(info.fileName(), info.absoluteFilePath()); + } +} + +void PostProcessingAddShaderListWidget::OnShadersSelected() +{ + m_chosen_shader_pathes.clear(); + if (m_shader_list->selectedItems().empty()) + return; + for (auto item : m_shader_list->selectedItems()) + { + m_chosen_shader_pathes.push_back(item->data(Qt::UserRole).toString()); + } +} + +void PostProcessingAddShaderListWidget::OnShaderTypeChanged() +{ + const int current_index = m_shader_type->currentIndex(); + if (current_index == -1) + return; + const auto available_shaders = + GetAvailableShaders(m_shader_type->itemData(current_index).toString().toStdString()); + m_shader_list->clear(); + for (const std::string& shader : available_shaders) + { + std::string name; + SplitPath(shader, nullptr, &name, nullptr); + QListWidgetItem* item = new QListWidgetItem(QString::fromStdString(name)); + item->setData(Qt::UserRole, QString::fromStdString(shader)); + m_shader_list->addItem(item); + } +} + +void PostProcessingAddShaderListWidget::OnSearchTextChanged(QString search_text) +{ + for (int i = 0; i < m_shader_list->count(); i++) + m_shader_list->item(i)->setHidden(true); + + const QList matched_items = + m_shader_list->findItems(search_text, Qt::MatchFlag::MatchContains); + for (auto* item : matched_items) + item->setHidden(false); +} + +std::vector +PostProcessingAddShaderListWidget::GetAvailableShaders(const std::string& directory_path) +{ + const std::vector search_dirs = {directory_path}; + const std::vector search_extensions = {".glsl"}; + std::vector result; + std::vector paths; + + // main folder + paths = Common::DoFileSearch(search_dirs, search_extensions, false); + for (const std::string& path : paths) + { + if (std::find(result.begin(), result.end(), path) == result.end()) + result.push_back(path); + } + + // folders/sub-shaders + // paths = Common::DoFileSearch(search_dirs, false); + for (const std::string& dirname : paths) + { + // find sub-shaders in this folder + size_t pos = dirname.find_last_of(DIR_SEP_CHR); + if (pos != std::string::npos && (pos != dirname.length() - 1)) + { + std::string shader_dirname = dirname.substr(pos + 1); + std::vector sub_paths = + Common::DoFileSearch(search_extensions, {dirname}, false); + for (const std::string& sub_path : sub_paths) + { + if (std::find(result.begin(), result.end(), sub_path) == result.end()) + result.push_back(sub_path); + } + } + } + + // sort by path + std::sort(result.begin(), result.end()); + return result; +} + +void PostProcessingAddShaderListWidget::dragEnterEvent(QDragEnterEvent* event) +{ + if (event->mimeData()->hasUrls() && event->mimeData()->urls().size() == 1) + event->acceptProposedAction(); +} + +void PostProcessingAddShaderListWidget::dropEvent(QDropEvent* event) +{ + const auto& urls = event->mimeData()->urls(); + if (urls.empty()) + return; + + const auto& url = urls[0]; + QFileInfo file_info(url.toLocalFile()); + + if (!file_info.exists() || !file_info.isReadable()) + { + ModalMessageBox::critical(this, tr("Error"), + tr("Path '%1' is not readable").arg(file_info.filePath())); + return; + } + + if (file_info.isDir()) + { + const auto file_path = file_info.absoluteFilePath().toStdString(); + const std::vector search_dirs = {file_path}; + const std::vector search_extensions = {".glsl"}; + const auto pathes = Common::DoFileSearch(search_dirs, search_extensions, false); + if (pathes.empty()) + { + ModalMessageBox::critical( + this, tr("Error"), + tr("Folder '%1' must container a shader that ends in glsl").arg(file_info.filePath())); + return; + } + + const auto root_path = m_root_path.filePath(file_info.fileName()).toStdString(); + File::CopyDir(file_path, root_path + DIR_SEP_CHR, false); + } + else if (file_info.isFile()) + { + if (file_info.completeSuffix().toLower() != QStringLiteral("glsl")) + { + ModalMessageBox::critical( + this, tr("Error"), + tr("File '%1' must be a shader that ends in glsl").arg(file_info.filePath())); + return; + } + + const auto file_path = file_info.absoluteFilePath().toStdString(); + const auto root_file = m_root_path.filePath(file_info.fileName()).toStdString(); + File::Copy(file_path, root_file); + } + + BuildShaderCategories(); + OnShaderTypeChanged(); +} diff --git a/Source/Core/DolphinQt/Config/Graphics/PostProcessingAddShaderListWidget.h b/Source/Core/DolphinQt/Config/Graphics/PostProcessingAddShaderListWidget.h new file mode 100644 index 000000000000..e8b4bc688836 --- /dev/null +++ b/Source/Core/DolphinQt/Config/Graphics/PostProcessingAddShaderListWidget.h @@ -0,0 +1,49 @@ +// Copyright 2020 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include + +#include + +class QComboBox; +class QLineEdit; +class QListWidget; +class QVBoxLayout; + +class PostProcessingAddShaderListWidget final : public QWidget +{ + Q_OBJECT +public: + PostProcessingAddShaderListWidget(QWidget* parent, const QString& root_path, bool allows_drag_drop); + + std::vector ChosenShaderPathes() const { return m_chosen_shader_pathes; } + +private: + void CreateWidgets(); + void ConnectWidgets(); + + void BuildShaderCategories(); + + void OnShadersSelected(); + + void OnShaderTypeChanged(); + void OnSearchTextChanged(QString search_text); + static std::vector GetAvailableShaders(const std::string& directory_path); + + void dragEnterEvent(QDragEnterEvent* event) override; + void dropEvent(QDropEvent* event) override; + + QVBoxLayout* m_main_layout; + QListWidget* m_shader_list; + QComboBox* m_shader_type; + QLineEdit* m_search_text; + + QDir m_root_path; + bool m_allows_drag_drop; + std::vector m_chosen_shader_pathes; +}; diff --git a/Source/Core/DolphinQt/Config/Graphics/PostProcessingConfigWindow.cpp b/Source/Core/DolphinQt/Config/Graphics/PostProcessingConfigWindow.cpp deleted file mode 100644 index 434aaa6d2f82..000000000000 --- a/Source/Core/DolphinQt/Config/Graphics/PostProcessingConfigWindow.cpp +++ /dev/null @@ -1,362 +0,0 @@ -// Copyright 2018 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "DolphinQt/Config/Graphics/PostProcessingConfigWindow.h" - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "DolphinQt/Config/Graphics/EnhancementsWidget.h" - -#include "VideoCommon/PostProcessing.h" -#include "VideoCommon/RenderBase.h" -#include "VideoCommon/VideoConfig.h" - -using ConfigurationOption = VideoCommon::PostProcessingConfiguration::ConfigurationOption; -using OptionType = ConfigurationOption::OptionType; - -PostProcessingConfigWindow::PostProcessingConfigWindow(EnhancementsWidget* parent, - const std::string& shader) - : QDialog(parent), m_shader(shader) -{ - if (g_renderer && g_renderer->GetPostProcessor()) - { - m_post_processor = g_renderer->GetPostProcessor()->GetConfig(); - } - else - { - m_post_processor = new VideoCommon::PostProcessingConfiguration(); - m_post_processor->LoadShader(m_shader); - } - - setWindowTitle(tr("Post-Processing Shader Configuration")); - setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); - - PopulateGroups(); - Create(); - ConnectWidgets(); -} - -PostProcessingConfigWindow::~PostProcessingConfigWindow() -{ - m_post_processor->SaveOptionsConfiguration(); - if (!(g_renderer && g_renderer->GetPostProcessor())) - { - delete m_post_processor; - } -} - -void PostProcessingConfigWindow::PopulateGroups() -{ - const VideoCommon::PostProcessingConfiguration::ConfigMap& config_map = - m_post_processor->GetOptions(); - - auto config_groups = std::vector>(); - for (const auto& it : config_map) - { - auto config_group = std::make_unique(&it.second); - m_config_map[it.first] = config_group.get(); - config_groups.push_back(std::move(config_group)); - } - - for (auto& config_group : config_groups) - { - const std::string& parent_name = config_group->GetParent(); - if (parent_name.empty()) - { - m_config_groups.emplace_back(std::move(config_group)); - } - else - { - m_config_map[parent_name]->AddSubGroup(std::move(config_group)); - } - } -} - -void PostProcessingConfigWindow::Create() -{ - m_tabs = new QTabWidget(); - auto* const general = new QWidget(m_tabs); - auto* const general_layout = new QGridLayout(general); - - u32 row = 0; - bool add_general_page = false; - for (const auto& it : m_config_groups) - { - if (it->HasSubGroups()) - { - auto* const tab = CreateDependentTab(it); - m_tabs->addTab(tab, QString::fromStdString(it->GetGUIName())); - } - else - { - if (!add_general_page) - { - add_general_page = true; - } - row = it->AddWidgets(this, general_layout, row); - } - } - - if (add_general_page) - { - m_tabs->insertTab(0, general, tr("General")); - } - - m_buttons = new QDialogButtonBox(QDialogButtonBox::Ok); - - auto* layout = new QVBoxLayout(this); - layout->addWidget(m_tabs); - layout->addWidget(m_buttons); -} - -void PostProcessingConfigWindow::ConnectWidgets() -{ - connect(m_buttons, &QDialogButtonBox::accepted, this, &PostProcessingConfigWindow::accept); -} - -QWidget* -PostProcessingConfigWindow::CreateDependentTab(const std::unique_ptr& config_group) -{ - auto* const tab = new QWidget(m_tabs); - auto* const layout = new QGridLayout(tab); - - u32 row = config_group->AddWidgets(this, layout, 0); - for (const auto& child : config_group->GetSubGroups()) - { - row = child->AddWidgets(this, layout, row); - } - - return tab; -} - -void PostProcessingConfigWindow::UpdateBool(ConfigGroup* const config_group, const bool state) -{ - m_post_processor->SetOptionb(config_group->GetOptionName(), state); - - config_group->EnableSuboptions(state); -} - -void PostProcessingConfigWindow::UpdateInteger(ConfigGroup* const config_group, const int value) -{ - const ConfigurationOption& config_option = - m_post_processor->GetOption(config_group->GetOptionName()); - - const size_t vector_size = config_option.m_integer_values.size(); - - for (size_t i = 0; i < vector_size; ++i) - { - const int current_step = config_group->GetSliderValue(i); - const s32 current_value = config_option.m_integer_step_values[i] * current_step + - config_option.m_integer_min_values[i]; - m_post_processor->SetOptioni(config_option.m_option_name, static_cast(i), current_value); - config_group->SetSliderText(i, QString::number(current_value)); - } -} - -void PostProcessingConfigWindow::UpdateFloat(ConfigGroup* const config_group, const int value) -{ - const ConfigurationOption& config_option = - m_post_processor->GetOption(config_group->GetOptionName()); - - const size_t vector_size = config_option.m_float_values.size(); - - for (size_t i = 0; i < vector_size; ++i) - { - const int current_step = config_group->GetSliderValue(static_cast(i)); - const float current_value = - config_option.m_float_step_values[i] * current_step + config_option.m_float_min_values[i]; - m_post_processor->SetOptionf(config_option.m_option_name, static_cast(i), current_value); - config_group->SetSliderText(i, QString::asprintf("%f", current_value)); - } -} - -PostProcessingConfigWindow::ConfigGroup::ConfigGroup(const ConfigurationOption* config_option) - : m_config_option(config_option) -{ -} - -const std::string& PostProcessingConfigWindow::ConfigGroup::GetGUIName() const noexcept -{ - return m_config_option->m_gui_name; -} - -const std::string& PostProcessingConfigWindow::ConfigGroup::GetParent() const noexcept -{ - return m_config_option->m_dependent_option; -} - -const std::string& PostProcessingConfigWindow::ConfigGroup::GetOptionName() const noexcept -{ - return m_config_option->m_option_name; -} - -void PostProcessingConfigWindow::ConfigGroup::AddSubGroup(std::unique_ptr&& subgroup) -{ - m_subgroups.emplace_back(std::move(subgroup)); -} - -bool PostProcessingConfigWindow::ConfigGroup::HasSubGroups() const noexcept -{ - return !m_subgroups.empty(); -} - -const std::vector>& -PostProcessingConfigWindow::ConfigGroup::GetSubGroups() const noexcept -{ - return m_subgroups; -} - -u32 PostProcessingConfigWindow::ConfigGroup::AddWidgets(PostProcessingConfigWindow* const parent, - QGridLayout* const grid, const u32 row) -{ - auto* const name = new QLabel(QString::fromStdString(m_config_option->m_gui_name)); - grid->addWidget(name, row, 0); - - switch (m_config_option->m_type) - { - case OptionType::OPTION_BOOL: - return AddBool(parent, grid, row); - case OptionType::OPTION_FLOAT: - return AddFloat(parent, grid, row); - case OptionType::OPTION_INTEGER: - return AddInteger(parent, grid, row); - default: - // obviously shouldn't get here - std::abort(); - } -} - -u32 PostProcessingConfigWindow::ConfigGroup::AddBool(PostProcessingConfigWindow* const parent, - QGridLayout* const grid, const u32 row) -{ - m_checkbox = new QCheckBox(); - m_checkbox->setChecked(m_config_option->m_bool_value); - QObject::connect(m_checkbox, &QCheckBox::toggled, - [this, parent](bool checked) { parent->UpdateBool(this, checked); }); - grid->addWidget(m_checkbox, row, 2); - - return row + 1; -} - -u32 PostProcessingConfigWindow::ConfigGroup::AddInteger(PostProcessingConfigWindow* const parent, - QGridLayout* const grid, u32 row) -{ - const size_t vector_size = m_config_option->m_integer_values.size(); - - for (size_t i = 0; i < vector_size; ++i) - { - const int current_value = m_config_option->m_integer_values[i]; - const double range = - m_config_option->m_integer_max_values[i] - m_config_option->m_integer_min_values[i]; - // "How many steps we have is the range divided by the step interval configured. - // This may not be 100% spot on accurate since developers can have odd stepping intervals - // set. - // Round up so if it is outside our range, then set it to the minimum or maximum" - const int steps = - std::ceil(range / static_cast(m_config_option->m_integer_step_values[i])); - - auto* const slider = new QSlider(Qt::Orientation::Horizontal); - slider->setMinimum(0); - slider->setMaximum(steps); - slider->setValue(current_value); - slider->setTickInterval(range / steps); - QObject::connect(slider, &QSlider::valueChanged, - [this, parent](int value) { parent->UpdateInteger(this, value); }); - - auto* const value_box = new QLineEdit(QString::number(current_value)); - value_box->setEnabled(false); - - grid->addWidget(slider, row, 1); - grid->addWidget(value_box, row, 2); - - m_sliders.push_back(slider); - m_value_boxes.push_back(value_box); - if (vector_size > 1) - { - row++; - } - } - - return row + 1; -} - -u32 PostProcessingConfigWindow::ConfigGroup::AddFloat(PostProcessingConfigWindow* const parent, - QGridLayout* const grid, u32 row) -{ - const size_t vector_size = m_config_option->m_float_values.size(); - - for (size_t i = 0; i < vector_size; ++i) - { - const int current_value = - m_config_option->m_float_values[i] / m_config_option->m_float_step_values[i]; - const float range = - m_config_option->m_float_max_values[i] - m_config_option->m_float_min_values[i]; - const int steps = std::ceil(range / m_config_option->m_float_step_values[i]); - - auto* const slider = new QSlider(Qt::Orientation::Horizontal); - slider->setMinimum(0); - slider->setMaximum(steps); - slider->setValue(current_value); - slider->setTickInterval(range / steps); - QObject::connect(slider, &QSlider::valueChanged, - [this, parent](int value) { parent->UpdateFloat(this, value); }); - - auto* const value_box = - new QLineEdit(QString::asprintf("%f", m_config_option->m_float_values[i])); - value_box->setEnabled(false); - - grid->addWidget(slider, row, 1); - grid->addWidget(value_box, row, 2); - - m_sliders.push_back(slider); - m_value_boxes.push_back(value_box); - if (vector_size > 1) - { - row++; - } - } - - return row + 1; -} - -void PostProcessingConfigWindow::ConfigGroup::EnableSuboptions(const bool state) -{ - for (auto& it : m_subgroups) - { - if (it->m_config_option->m_type == OptionType::OPTION_BOOL) - { - it->m_checkbox->setEnabled(state); - } - else - { - for (auto& slider : it->m_sliders) - { - slider->setEnabled(state); - } - } - it->EnableSuboptions(state); - } -} - -int PostProcessingConfigWindow::ConfigGroup::GetSliderValue(size_t index) const -{ - return m_sliders[index]->value(); -} - -void PostProcessingConfigWindow::ConfigGroup::SetSliderText(size_t index, const QString& text) -{ - m_value_boxes[index]->setText(text); -} diff --git a/Source/Core/DolphinQt/Config/Graphics/PostProcessingConfigWindow.h b/Source/Core/DolphinQt/Config/Graphics/PostProcessingConfigWindow.h deleted file mode 100644 index 0117c05b8b0a..000000000000 --- a/Source/Core/DolphinQt/Config/Graphics/PostProcessingConfigWindow.h +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2018 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include -#include - -#include - -#include "Common/CommonTypes.h" -#include "VideoCommon/PostProcessing.h" - -class EnhancementsWidget; -class QCheckBox; -class QDialogButtonBox; -class QGridLayout; -class QLineEdit; -class QSlider; -class QTabWidget; -class QWidget; - -class PostProcessingConfigWindow final : public QDialog -{ - Q_OBJECT -public: - explicit PostProcessingConfigWindow(EnhancementsWidget* parent, const std::string& shader); - ~PostProcessingConfigWindow(); - -private: - class ConfigGroup final - { - public: - explicit ConfigGroup( - const VideoCommon::PostProcessingConfiguration::ConfigurationOption* config_option); - - const std::string& GetGUIName() const noexcept; - const std::string& GetParent() const noexcept; - const std::string& GetOptionName() const noexcept; - void AddSubGroup(std::unique_ptr&& subgroup); - bool HasSubGroups() const noexcept; - const std::vector>& GetSubGroups() const noexcept; - u32 AddWidgets(PostProcessingConfigWindow* parent, QGridLayout* grid, u32 row); - void EnableSuboptions(bool state); - int GetSliderValue(size_t index) const; - void SetSliderText(size_t index, const QString& text); - - private: - u32 AddBool(PostProcessingConfigWindow* parent, QGridLayout* grid, u32 row); - u32 AddInteger(PostProcessingConfigWindow* parent, QGridLayout* grid, u32 row); - u32 AddFloat(PostProcessingConfigWindow* parent, QGridLayout* grid, u32 row); - - QCheckBox* m_checkbox; - std::vector m_sliders; - std::vector m_value_boxes; - - const VideoCommon::PostProcessingConfiguration::ConfigurationOption* m_config_option; - std::vector> m_subgroups; - }; - void Create(); - void ConnectWidgets(); - QWidget* CreateDependentTab(const std::unique_ptr& config_group); - void PopulateGroups(); - void UpdateBool(ConfigGroup* config_group, bool state); - void UpdateInteger(ConfigGroup* config_group, int value); - void UpdateFloat(ConfigGroup* config_group, int value); - - QTabWidget* m_tabs; - QDialogButtonBox* m_buttons; - - const std::string& m_shader; - VideoCommon::PostProcessingConfiguration* m_post_processor; - std::unordered_map m_config_map; - std::vector> m_config_groups; -}; diff --git a/Source/Core/DolphinQt/Config/Graphics/PostProcessingShaderListWidget.cpp b/Source/Core/DolphinQt/Config/Graphics/PostProcessingShaderListWidget.cpp new file mode 100644 index 000000000000..abe73cfd4c59 --- /dev/null +++ b/Source/Core/DolphinQt/Config/Graphics/PostProcessingShaderListWidget.cpp @@ -0,0 +1,713 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "DolphinQt/Config/Graphics/PostProcessingShaderListWidget.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Common/VariantUtil.h" +#include "Core/Config/GraphicsSettings.h" +#include "Core/ConfigManager.h" +#include "Core/Core.h" + +#include "DolphinQt/Config/Graphics/GraphicsColor.h" +#include "DolphinQt/Config/Graphics/GraphicsImage.h" +#include "DolphinQt/Config/Graphics/PostProcessingAddShaderDialog.h" +#include "DolphinQt/Settings.h" + +#include "VideoCommon/PEShaderSystem/Config/PEShaderConfigInput.h" +#include "VideoCommon/PEShaderSystem/Config/PEShaderConfigOption.h" +#include "VideoCommon/PEShaderSystem/Config/PEShaderConfigPass.h" +#include "VideoCommon/VideoConfig.h" + +namespace +{ +struct OptionVisitor +{ + static inline const std::array vector_labels = { + QStringLiteral("X"), QStringLiteral("Y"), QStringLiteral("Z"), QStringLiteral("W")}; + + VideoCommon::PE::ShaderConfigGroup* m_group; + u32 m_shader_index; + QWidget* m_widget; + QGridLayout* m_option_layout; + int m_row_count; + + template + void operator()(VideoCommon::PE::FloatOption& option) + { + m_option_layout->addWidget(new QLabel(QString::fromStdString(option.m_common.m_ui_name)), + m_row_count, 0); + + auto* values_layout = new QHBoxLayout; + m_option_layout->addLayout(values_layout, m_row_count, 1); + for (std::size_t i = 0; i < option.m_value.size(); i++) + { + if constexpr (N > 1) + { + auto axis_label = new QLabel(vector_labels[i] + QStringLiteral(":")); + values_layout->addWidget(axis_label); + } + auto box = new QDoubleSpinBox; + if (option.m_decimal_precision[i] != 0.0) + { + box->setDecimals(static_cast(option.m_decimal_precision[i])); + } + box->setValue(static_cast(option.m_value[i])); + box->setMinimum(static_cast(option.m_min_value[i])); + box->setMaximum(static_cast(option.m_max_value[i])); + box->setSingleStep(static_cast(option.m_step_size[i])); + m_widget->connect( + box, QOverload::of(&QDoubleSpinBox::valueChanged), m_widget, + [&option, i, group = m_group, shader_index = m_shader_index](double value) mutable { + option.m_value[i] = static_cast(value); + + auto& shader = group->m_shaders[shader_index]; + if (option.m_common.m_compile_time_constant) + { + group->m_changes++; + } + else + { + shader.m_changes++; + } + }); + values_layout->addWidget(box); + } + } + + template + void operator()(VideoCommon::PE::IntOption& option) + { + m_option_layout->addWidget(new QLabel(QString::fromStdString(option.m_common.m_ui_name)), + m_row_count, 0); + + auto* values_layout = new QHBoxLayout; + m_option_layout->addLayout(values_layout, m_row_count, 1); + for (std::size_t i = 0; i < option.m_value.size(); i++) + { + if constexpr (N > 1) + { + auto axis_label = new QLabel(vector_labels[i] + QStringLiteral(":")); + values_layout->addWidget(axis_label); + } + auto box = new QSpinBox; + box->setValue(option.m_value[i]); + box->setMinimum(option.m_min_value[i]); + box->setMaximum(option.m_max_value[i]); + box->setSingleStep(option.m_step_size[i]); + m_widget->connect( + box, QOverload::of(&QSpinBox::valueChanged), m_widget, + [&option, i, group = m_group, shader_index = m_shader_index](int value) mutable { + option.m_value[i] = value; + + auto& shader = group->m_shaders[shader_index]; + if (option.m_common.m_compile_time_constant) + { + group->m_changes++; + } + else + { + shader.m_changes++; + } + }); + values_layout->addWidget(box); + } + } + + void operator()(VideoCommon::PE::EnumChoiceOption& option) + { + auto* combobox = new QComboBox; + for (const auto& choice : option.m_ui_choices) + { + combobox->addItem(QString::fromStdString(choice)); + } + combobox->setCurrentIndex(option.m_index); + m_widget->connect(combobox, QOverload::of(&QComboBox::currentIndexChanged), m_widget, + [&option, group = m_group, shader_index = m_shader_index](int index) { + option.m_index = static_cast(index); + + auto& shader = group->m_shaders[shader_index]; + if (option.m_common.m_compile_time_constant) + { + group->m_changes++; + } + else + { + shader.m_changes++; + } + }); + m_option_layout->addWidget(new QLabel(QString::fromStdString(option.m_common.m_ui_name)), + m_row_count, 0); + m_option_layout->addWidget(combobox, m_row_count, 1); + } + + void operator()(VideoCommon::PE::BoolOption& option) + { + auto* checkbox = new QCheckBox; + checkbox->setChecked(option.m_value); + m_widget->connect(checkbox, &QCheckBox::stateChanged, m_widget, + [&option, group = m_group, shader_index = m_shader_index](int state) { + option.m_value = state == 2; + + auto& shader = group->m_shaders[shader_index]; + if (option.m_common.m_compile_time_constant) + { + group->m_changes++; + } + else + { + shader.m_changes++; + } + }); + m_option_layout->addWidget(new QLabel(QString::fromStdString(option.m_common.m_ui_name)), + m_row_count, 0); + m_option_layout->addWidget(checkbox, m_row_count, 1); + } + + void operator()(VideoCommon::PE::ColorOption& option) + { + m_option_layout->addWidget(new QLabel(QString::fromStdString(option.m_common.m_ui_name)), + m_row_count, 0); + + auto* color_button = new GraphicsColor(m_widget, false); + + QColor initial_color; + initial_color.setRgbF(option.m_value[0], option.m_value[1], option.m_value[2]); + color_button->SetColor(initial_color); + m_widget->connect(color_button, &GraphicsColor::ColorIsUpdated, m_widget, + [&option, group = m_group, shader_index = m_shader_index, color_button]() { + const auto& color = color_button->GetColor(); + option.m_value[0] = static_cast(color.redF()); + option.m_value[1] = static_cast(color.greenF()); + option.m_value[2] = static_cast(color.blueF()); + + auto& shader = group->m_shaders[shader_index]; + if (option.m_common.m_compile_time_constant) + { + group->m_changes++; + } + else + { + shader.m_changes++; + } + }); + m_option_layout->addWidget(color_button, m_row_count, 1); + } + + void operator()(VideoCommon::PE::ColorAlphaOption& option) + { + m_option_layout->addWidget(new QLabel(QString::fromStdString(option.m_common.m_ui_name)), + m_row_count, 0); + + auto* color_button = new GraphicsColor(m_widget, true); + + QColor initial_color; + initial_color.setRgbF(option.m_value[0], option.m_value[1], option.m_value[2], + option.m_value[3]); + color_button->SetColor(initial_color); + m_widget->connect(color_button, &GraphicsColor::ColorIsUpdated, m_widget, + [&option, group = m_group, shader_index = m_shader_index, color_button]() { + const auto& color = color_button->GetColor(); + option.m_value[0] = static_cast(color.redF()); + option.m_value[1] = static_cast(color.greenF()); + option.m_value[2] = static_cast(color.blueF()); + option.m_value[3] = static_cast(color.alphaF()); + + auto& shader = group->m_shaders[shader_index]; + if (option.m_common.m_compile_time_constant) + { + group->m_changes++; + } + else + { + shader.m_changes++; + } + }); + m_option_layout->addWidget(color_button, m_row_count, 1); + } +}; +} // namespace + +PostProcessingShaderListWidget::PostProcessingShaderListWidget( + QWidget* parent, VideoCommon::PE::ShaderConfigGroup* group) + : QWidget(parent), m_group(group) +{ + CreateWidgets(); + ConnectWidgets(); + + RefreshShaderList(); +} + +void PostProcessingShaderListWidget::CreateWidgets() +{ + auto* main_layout = new QHBoxLayout(this); + + auto* left_v_layout = new QVBoxLayout; + + m_shader_list = new QListWidget; + m_shader_list->setSortingEnabled(false); + m_shader_list->setSelectionBehavior(QAbstractItemView::SelectionBehavior::SelectItems); + m_shader_list->setSelectionMode(QAbstractItemView::SelectionMode::SingleSelection); + m_shader_list->setSelectionRectVisible(true); + m_shader_list->setDragDropMode(QAbstractItemView::InternalMove); + + m_add_shader = new QPushButton(tr("Add...")); + m_remove_shader = new QPushButton(tr("Remove")); + QHBoxLayout* hlayout = new QHBoxLayout; + hlayout->addStretch(); + hlayout->addWidget(m_add_shader); + hlayout->addWidget(m_remove_shader); + + left_v_layout->addWidget(m_shader_list); + left_v_layout->addLayout(hlayout); + + auto* right_v_layout = new QVBoxLayout; + + auto default_shader_text = tr("No shader selected"); + m_selected_shader_name = new QLabel(default_shader_text); + right_v_layout->addWidget(m_selected_shader_name); + + m_shader_meta_layout = new QVBoxLayout; + right_v_layout->addLayout(m_shader_meta_layout); + + m_shader_buttons_layout = new QHBoxLayout; + right_v_layout->addLayout(m_shader_buttons_layout); + + m_passes_box = new QGroupBox(tr("Passes")); + auto* passes_v_layout = new QVBoxLayout; + m_passes_box->setLayout(passes_v_layout); + right_v_layout->addWidget(m_passes_box); + + m_options_snapshots_box = new QGroupBox(tr("Snapshots")); + auto* snapshots_v_layout = new QVBoxLayout; + m_options_snapshots_box->setLayout(snapshots_v_layout); + right_v_layout->addWidget(m_options_snapshots_box); + + m_options_box = new QGroupBox(tr("Options")); + auto* options_v_layout = new QVBoxLayout; + m_options_box->setLayout(options_v_layout); + right_v_layout->addWidget(m_options_box); + right_v_layout->addStretch(1); + + main_layout->addLayout(left_v_layout); + main_layout->addLayout(right_v_layout, 1); + + setLayout(main_layout); +} + +void PostProcessingShaderListWidget::ConnectWidgets() +{ + connect(m_shader_list, &QListWidget::itemSelectionChanged, this, + [this] { ShaderSelectionChanged(); }); + + connect(m_shader_list, &QListWidget::itemChanged, this, + &PostProcessingShaderListWidget::ShaderItemChanged); + + connect(m_shader_list->model(), &QAbstractItemModel::rowsMoved, this, + [this] { SaveShaderList(); }); + + connect(m_add_shader, &QPushButton::clicked, this, + &PostProcessingShaderListWidget::OnShaderAdded); + connect(m_remove_shader, &QPushButton::clicked, this, + &PostProcessingShaderListWidget::OnShaderRemoved); + + /* connect(&Settings::Instance(), &Settings::ShaderConfigLoaded, this, + [this] { RefreshShaderList(); });*/ +} + +void PostProcessingShaderListWidget::RefreshShaderList() +{ + m_shader_list->setCurrentItem(nullptr); + m_shader_list->clear(); + + for (const auto& index : m_group->m_shader_order) + { + const auto& shader = m_group->m_shaders[index]; + QListWidgetItem* item = new QListWidgetItem(QString::fromStdString(shader.m_name)); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + item->setData(Qt::UserRole, static_cast(index)); + + if (shader.m_enabled) + { + item->setCheckState(Qt::Checked); + } + else + { + item->setCheckState(Qt::Unchecked); + } + m_shader_list->addItem(item); + } +} + +void PostProcessingShaderListWidget::ShaderSelectionChanged() +{ + if (m_shader_list->currentItem() == nullptr) + return; + if (m_shader_list->count() == 0) + return; + const int index = m_shader_list->currentItem()->data(Qt::UserRole).toInt(); + OnShaderChanged(index); +} + +void PostProcessingShaderListWidget::ShaderItemChanged(QListWidgetItem* item) +{ + const int index = item->data(Qt::UserRole).toInt(); + auto& shader = m_group->m_shaders[index]; + const bool was_enabled = shader.m_enabled; + const bool should_enable = item->checkState() == Qt::Checked; + shader.m_enabled = should_enable; + if (was_enabled != should_enable) + { + m_group->m_changes++; + } +} + +void PostProcessingShaderListWidget::OnShaderChanged(std::optional index) +{ + QVBoxLayout* options_layout = static_cast(m_options_box->layout()); + ClearLayoutRecursively(options_layout); + + QVBoxLayout* passes_layout = static_cast(m_passes_box->layout()); + ClearLayoutRecursively(passes_layout); + + QVBoxLayout* snapshot_layout = static_cast(m_options_snapshots_box->layout()); + ClearLayoutRecursively(snapshot_layout); + + ClearLayoutRecursively(m_shader_meta_layout); + ClearLayoutRecursively(m_shader_buttons_layout); + + adjustSize(); + + if (!index) + { + m_selected_shader_name->setText(QStringLiteral("No shader selected")); + } + else + { + auto& shader = m_group->m_shaders[*index]; + m_selected_shader_name->setText(QString::fromStdString(shader.m_name)); + QFont font = m_selected_shader_name->font(); + font.setWeight(QFont::Bold); + m_selected_shader_name->setFont(font); + + if (!shader.m_author.empty()) + { + auto* author_label = new QLabel(QString::fromStdString(shader.m_author)); + m_shader_meta_layout->addWidget(author_label); + } + + if (!shader.m_description.empty()) + { + auto* description_label = new QLabel(QString::fromStdString(shader.m_description)); + m_shader_meta_layout->addWidget(description_label); + } + + auto reset_button = new QPushButton(tr("Reset to Defaults")); + connect(reset_button, &QPushButton::clicked, [index, this] { + m_group->m_shaders[*index].Reset(); + OnShaderChanged(index); + }); + + auto reload_button = new QPushButton(tr("Reload")); + connect(reload_button, &QPushButton::clicked, [index, this] { + m_group->m_changes++; + OnShaderChanged(index); + }); + + m_shader_buttons_layout->addWidget(reset_button); + m_shader_buttons_layout->addWidget(reload_button); + + const bool has_error = shader.m_runtime_info && shader.m_runtime_info->HasError(); + + if (std::none_of(shader.m_passes.begin(), shader.m_passes.end(), + [](const VideoCommon::PE::ShaderConfigPass& pass) { + return std::any_of(pass.m_inputs.begin(), pass.m_inputs.end(), + [](const VideoCommon::PE::ShaderConfigInput& input) { + return std::get_if( + &input) != nullptr; + }); + })) + { + passes_layout->addWidget(new QLabel(QStringLiteral("No passes are editable"))); + } + else + { + if (has_error) + { + passes_layout->addWidget( + new QLabel(QStringLiteral("Shader error, fix and reload to edit passes"))); + } + else + { + passes_layout->addLayout(BuildPassesLayout(*index)); + } + } + + if (shader.m_options.empty()) + { + options_layout->addWidget(new QLabel(QStringLiteral("No options available"))); + } + else + { + if (has_error) + { + options_layout->addWidget( + new QLabel(QStringLiteral("Shader error, fix and reload to edit options"))); + } + else + { + snapshot_layout->addLayout(BuildSnapshotsLayout(*index)); + options_layout->addLayout(BuildOptionsLayout(*index)); + } + } + } +} + +QLayout* PostProcessingShaderListWidget::BuildPassesLayout(u32 index) +{ + auto& shader = m_group->m_shaders[index]; + QVBoxLayout* passes_layout = new QVBoxLayout; + for (std::size_t i = 0; i < shader.m_passes.size(); i++) + { + auto& pass = shader.m_passes[i]; + QGridLayout* pass_layout = new QGridLayout; + pass_layout->addWidget(new QLabel(QStringLiteral("Pass %1").arg(i)), 0, 0, 1, 2); + + int input_row = 1; + for (auto& input : pass.m_inputs) + { + std::visit( + [&, this](auto&& input) { + using T = std::decay_t; + if constexpr (std::is_same_v) + { + pass_layout->addWidget( + new QLabel(QStringLiteral("Input%1 Image Source").arg(input.m_texture_unit)), + input_row, 0); + + GraphicsImage* image = new GraphicsImage(this); + connect(image, &GraphicsImage::PathIsUpdated, this, + [image, &input, group = m_group] { + input.m_path = image->GetPath(); + group->m_changes++; + }); + pass_layout->addWidget(image, input_row, 1); + + input_row++; + } + }, + input); + } + + passes_layout->addLayout(pass_layout); + } + return passes_layout; +} + +QLayout* PostProcessingShaderListWidget::BuildSnapshotsLayout(u32 index) +{ + QGridLayout* snapshots_layout = new QGridLayout; + + const int max_snapshots = static_cast(VideoCommon::PE::MAX_SNAPSHOTS); + + for (int column = 0; column < max_snapshots; column++) + { + auto* label = new QLabel(QString::fromStdString(std::to_string(column + 1))); + snapshots_layout->addWidget(label, 0, column, Qt::AlignCenter); + } + + auto& shader = m_group->m_shaders[index]; + for (int row = 1; row < 3; row++) + { + for (int column = 0; column < max_snapshots; column++) + { + QPushButton* button; + if (row == 1) + { + button = new QPushButton(tr("L")); + if (shader.HasSnapshot(column)) + { + button->setEnabled(true); + connect(button, &QPushButton::clicked, this, [index, column, this]() { + auto& shader = m_group->m_shaders[index]; + shader.LoadSnapshot(column); + OnShaderChanged(index); + }); + } + else + { + button->setEnabled(false); + } + } + else + { + button = new QPushButton(tr("S")); + connect(button, &QPushButton::clicked, this, [index, column, this]() { + auto& shader = m_group->m_shaders[index]; + shader.SaveSnapshot(column); + }); + } + + button->setMaximumWidth(button->fontMetrics().boundingRect(button->text()).width() * 4); + + snapshots_layout->addWidget(button, row, column, Qt::AlignCenter); + } + } + + return snapshots_layout; +} + +QLayout* PostProcessingShaderListWidget::BuildOptionsLayout(u32 index) +{ + int rows = 0; + + QVBoxLayout* options_layout = new QVBoxLayout; + + QTabWidget* tabs_widget = new QTabWidget; + + connect(tabs_widget, &QTabWidget::currentChanged, this, [tabs_widget, this](int index) { + for (int i = 0; i < tabs_widget->count(); i++) + { + if (i != index) + tabs_widget->widget(i)->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); + } + + auto* widget = tabs_widget->widget(index); + widget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + widget->resize(widget->minimumSizeHint()); + widget->adjustSize(); + resize(minimumSizeHint()); + adjustSize(); + }); + + options_layout->addWidget(tabs_widget); + + auto& shader = m_group->m_shaders[index]; + auto groups = shader.GetGroups(); + for (auto& [group_name, options] : groups) + { + QWidget* group_widget = new QWidget; + QGridLayout* option_layout = new QGridLayout; + + group_widget->setLayout(option_layout); + + tabs_widget->addTab(group_widget, QString::fromStdString(group_name)); + + for (auto* option : options) + { + OptionVisitor visitor; + visitor.m_option_layout = option_layout; + visitor.m_widget = this; + visitor.m_shader_index = index; + visitor.m_group = m_group; + visitor.m_row_count = rows; + std::visit(visitor, *option); + + rows++; + } + } + + return options_layout; +} + +void PostProcessingShaderListWidget::SaveShaderList() +{ + std::vector shader_order; + shader_order.resize(m_shader_list->count()); + for (int i = 0; i < m_shader_list->count(); ++i) + { + QListWidgetItem* item = m_shader_list->item(i); + const int index = item->data(Qt::UserRole).toInt(); + shader_order[i] = static_cast(index); + } + + m_group->m_shader_order = shader_order; + m_group->m_changes++; +} + +void PostProcessingShaderListWidget::ClearLayoutRecursively(QLayout* layout) +{ + while (QLayoutItem* child = layout->takeAt(0)) + { + if (child == nullptr) + continue; + + if (child->widget()) + { + layout->removeWidget(child->widget()); + delete child->widget(); + } + else if (child->layout()) + { + ClearLayoutRecursively(child->layout()); + layout->removeItem(child); + } + else + { + layout->removeItem(child); + } + delete child; + } +} + +void PostProcessingShaderListWidget::OnShaderAdded() +{ + PostProcessingAddShaderDialog add_shader_dialog(this); + if (add_shader_dialog.exec() == QDialog::Accepted) + { + bool refresh_list = false; + + const auto user_shader_pathes = add_shader_dialog.ChosenUserShaderPathes(); + if (!user_shader_pathes.empty()) + { + for (const QString& shader_path : user_shader_pathes) + { + m_group->AddShader(shader_path.toStdString(), VideoCommon::PE::ShaderConfig::Source::User); + } + refresh_list = true; + } + + const auto system_shader_pathes = add_shader_dialog.ChosenSystemShaderPathes(); + if (!system_shader_pathes.empty()) + { + for (const QString& shader_path : system_shader_pathes) + { + m_group->AddShader(shader_path.toStdString(), VideoCommon::PE::ShaderConfig::Source::System); + } + refresh_list = true; + } + + if (refresh_list) + { + RefreshShaderList(); + } + } +} + +void PostProcessingShaderListWidget::OnShaderRemoved() +{ + if (m_shader_list->currentItem() == nullptr) + return; + const int index = m_shader_list->currentItem()->data(Qt::UserRole).toInt(); + m_group->RemoveShader(index); + + RefreshShaderList(); + OnShaderChanged(std::nullopt); +} diff --git a/Source/Core/DolphinQt/Config/Graphics/PostProcessingShaderListWidget.h b/Source/Core/DolphinQt/Config/Graphics/PostProcessingShaderListWidget.h new file mode 100644 index 000000000000..7420fd0da66a --- /dev/null +++ b/Source/Core/DolphinQt/Config/Graphics/PostProcessingShaderListWidget.h @@ -0,0 +1,63 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include + +#include + +#include "Common/CommonTypes.h" + +#include "VideoCommon/PEShaderSystem/Config/PEShaderConfig.h" +#include "VideoCommon/PEShaderSystem/Config/PEShaderConfigGroup.h" + +class QGroupBox; +class QHBoxLayout; +class QLabel; +class QListWidget; +class QListWidgetItem; +class QPushButton; +class QVBoxLayout; + +class PostProcessingShaderListWidget final : public QWidget +{ + Q_OBJECT +public: + PostProcessingShaderListWidget(QWidget* parent, VideoCommon::PE::ShaderConfigGroup* group); + +private: + void CreateWidgets(); + void ConnectWidgets(); + + void RefreshShaderList(); + void ShaderSelectionChanged(); + void ShaderItemChanged(QListWidgetItem* item); + + void OnShaderChanged(std::optional index); + QLayout* BuildPassesLayout(u32 index); + QLayout* BuildSnapshotsLayout(u32 index); + QLayout* BuildOptionsLayout(u32 index); + + void SaveShaderList(); + + void OnShaderAdded(); + void OnShaderRemoved(); + + void ClearLayoutRecursively(QLayout* layout); + + VideoCommon::PE::ShaderConfigGroup* m_group; + + QListWidget* m_shader_list; + + QGroupBox* m_passes_box; + QGroupBox* m_options_snapshots_box; + QGroupBox* m_options_box; + QLabel* m_selected_shader_name; + QVBoxLayout* m_shader_meta_layout; + QHBoxLayout* m_shader_buttons_layout; + + QPushButton* m_add_shader; + QPushButton* m_remove_shader; +}; diff --git a/Source/Core/DolphinQt/Config/Graphics/Trigger/GraphicsTriggerSelectionDialog.cpp b/Source/Core/DolphinQt/Config/Graphics/Trigger/GraphicsTriggerSelectionDialog.cpp new file mode 100644 index 000000000000..2ed2b9053f02 --- /dev/null +++ b/Source/Core/DolphinQt/Config/Graphics/Trigger/GraphicsTriggerSelectionDialog.cpp @@ -0,0 +1,92 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "DolphinQt/Config/Graphics/Trigger/GraphicsTriggerSelectionDialog.h" + +#include +#include +#include +#include +#include + +#include "DolphinQt/Config/ToolTipControls/ToolTipComboBox.h" + +#include "Common/FileSearch.h" +#include "Common/FileUtil.h" +#include "VideoCommon/GraphicsTrigger.h" +#include "VideoCommon/GraphicsTriggerManager.h" + +GraphicsTriggerSelectionDialog::GraphicsTriggerSelectionDialog(QWidget* parent) : QDialog(parent) +{ + CreateMainLayout(); + ConnectWidgets(); + + setWindowTitle(tr("Select Trigger")); + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); +} + +QString GraphicsTriggerSelectionDialog::GetTriggerName() const +{ + return m_triggers->currentText(); +} + +void GraphicsTriggerSelectionDialog::CreateMainLayout() +{ + auto* main_layout = new QVBoxLayout(); + + m_button_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + connect(m_button_box, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(m_button_box, &QDialogButtonBox::rejected, this, &QDialog::reject); + //m_button_box->button(QDialogButtonBox::Ok)->setEnabled(false); + + auto form_layout = new QFormLayout; + + m_triggers = new ToolTipComboBox; + BuildComboBox(); + + form_layout->addRow(tr("Trigger:"), m_triggers); + + main_layout->addLayout(form_layout); + main_layout->addWidget(m_button_box); + setLayout(main_layout); +} + +void GraphicsTriggerSelectionDialog::BuildComboBox() +{ + m_triggers->clear(); + + std::vector items; + + const std::string user_profiles_path = + File::GetUserPath(D_CONFIG_IDX) + GraphicsTriggerManager::directory_path; + for (const auto& filename : Common::DoFileSearch({user_profiles_path}, {".ini"})) + { + std::string basename; + SplitPath(filename, nullptr, &basename, nullptr); + items.push_back(basename); + } + + const std::string system_profiles_path = + File::GetSysDirectory() + GraphicsTriggerManager::directory_path; + for (const auto& filename : Common::DoFileSearch({system_profiles_path}, {".ini"})) + { + std::string basename; + SplitPath(filename, nullptr, &basename, nullptr); + items.push_back(basename); + } + + std::sort(items.begin(), items.end()); + + for (const auto& item : items) + { + m_triggers->addItem(QString::fromStdString(item)); + } +} + +void GraphicsTriggerSelectionDialog::ConnectWidgets() +{ + connect(m_triggers, qOverload(&QComboBox::currentIndexChanged), this, + [this](int) { m_button_box->button(QDialogButtonBox::Ok)->setEnabled(true); }); +} + diff --git a/Source/Core/DolphinQt/Config/Graphics/Trigger/GraphicsTriggerSelectionDialog.h b/Source/Core/DolphinQt/Config/Graphics/Trigger/GraphicsTriggerSelectionDialog.h new file mode 100644 index 000000000000..e635511920c5 --- /dev/null +++ b/Source/Core/DolphinQt/Config/Graphics/Trigger/GraphicsTriggerSelectionDialog.h @@ -0,0 +1,27 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include + +class QDialogButtonBox; +class ToolTipComboBox; + +class GraphicsTriggerSelectionDialog final : public QDialog +{ + Q_OBJECT +public: + explicit GraphicsTriggerSelectionDialog(QWidget* parent); + + QString GetTriggerName() const; + +private: + void CreateMainLayout(); + void BuildComboBox(); + void ConnectWidgets(); + + ToolTipComboBox* m_triggers; + QDialogButtonBox* m_button_box; +}; diff --git a/Source/Core/DolphinQt/Config/Graphics/Trigger/GraphicsTriggerWidget.cpp b/Source/Core/DolphinQt/Config/Graphics/Trigger/GraphicsTriggerWidget.cpp new file mode 100644 index 000000000000..ebbd83bb7eea --- /dev/null +++ b/Source/Core/DolphinQt/Config/Graphics/Trigger/GraphicsTriggerWidget.cpp @@ -0,0 +1,138 @@ +#include "DolphinQt/Config/Graphics/Trigger/GraphicsTriggerWidget.h" + +#include +#include +#include +#include +#include +#include + +#include "Common/FileSearch.h" +#include "Common/FileUtil.h" +#include "DolphinQt/Config/Graphics/Trigger/NewDrawCall2DTriggerDialog.h" +#include "DolphinQt/Config/Graphics/Trigger/NewDrawCall3DTriggerDialog.h" +#include "DolphinQt/Config/Graphics/Trigger/NewEFBTriggerDialog.h" +#include "DolphinQt/Config/Graphics/Trigger/NewTextureLoadTriggerDialog.h" +#include "VideoCommon/GraphicsTriggerManager.h" + +GraphicsTriggerWidget::GraphicsTriggerWidget(QWidget* parent) : QWidget(parent) +{ + CreateLayout(); + LoadSettings(); + ReloadTriggerList(); +} + +void GraphicsTriggerWidget::LoadSettings() +{ + const std::string profiles_path = + File::GetUserPath(D_CONFIG_IDX) + GraphicsTriggerManager::directory_path; + for (const auto& filename : Common::DoFileSearch({profiles_path}, {".ini"})) + { + GraphicsTrigger trigger; + if (!LoadFromFile(trigger, filename)) + { + continue; + } + + std::string basename; + SplitPath(filename, nullptr, &basename, nullptr); + m_user_triggers.insert_or_assign(basename, trigger); + } +} + +void GraphicsTriggerWidget::SaveSettings() +{ +} + +void GraphicsTriggerWidget::CreateLayout() +{ + auto* main_layout = new QVBoxLayout; + + m_triggers = new QListWidget(); + main_layout->addWidget(m_triggers); + + m_add_trigger = new QToolButton(); + m_add_trigger_menu = new QMenu(m_add_trigger); + + auto add_efb = new QAction(tr("EFB..."), m_add_trigger_menu); + connect(add_efb, &QAction::triggered, [this] { AddEFBTrigger(); }); + + auto add_texture_load = new QAction(tr("Texture Load..."), m_add_trigger_menu); + connect(add_texture_load, &QAction::triggered, [this] { AddTextureLoadTrigger(); }); + + auto add_3ddraw = new QAction(tr("3D Draw Call..."), m_add_trigger_menu); + connect(add_3ddraw, &QAction::triggered, [this] { Add3DDrawCallTrigger(); }); + + auto add_2ddraw = new QAction(tr("2D Draw Call..."), m_add_trigger_menu); + connect(add_2ddraw, &QAction::triggered, [this] { Add2DDrawCallTrigger(); }); + + m_add_trigger_menu->addAction(add_efb); + m_add_trigger_menu->addAction(add_texture_load); + m_add_trigger_menu->addAction(add_3ddraw); + m_add_trigger_menu->addAction(add_2ddraw); + m_add_trigger_menu->setDefaultAction(add_efb); + m_add_trigger->setPopupMode(QToolButton::MenuButtonPopup); + m_add_trigger->setMenu(m_add_trigger_menu); + + m_remove_trigger = new QPushButton(tr("Remove")); + QHBoxLayout* hlayout = new QHBoxLayout; + hlayout->addStretch(); + hlayout->addWidget(m_add_trigger); + hlayout->addWidget(m_remove_trigger); + + main_layout->addItem(hlayout); + setLayout(main_layout); +} + +void GraphicsTriggerWidget::ReloadTriggerList() +{ + m_triggers->clear(); + for (const auto& [name, trigger] : m_user_triggers) + { + m_triggers->addItem(QString::fromStdString(name)); + } +} + +void GraphicsTriggerWidget::AddEFBTrigger() +{ + NewEFBTriggerDialog trigger_dialog(this); + if (trigger_dialog.exec() == QDialog::Accepted) + { + m_user_triggers.insert_or_assign(trigger_dialog.GetName().toStdString(), + trigger_dialog.GetTrigger()); + ReloadTriggerList(); + } +} + +void GraphicsTriggerWidget::Add2DDrawCallTrigger() +{ + NewDrawCall2DTriggerDialog trigger_dialog(this); + if (trigger_dialog.exec() == QDialog::Accepted) + { + m_user_triggers.insert_or_assign(trigger_dialog.GetName().toStdString(), + trigger_dialog.GetTrigger()); + ReloadTriggerList(); + } +} + +void GraphicsTriggerWidget::Add3DDrawCallTrigger() +{ + NewDrawCall3DTriggerDialog trigger_dialog(this); + if (trigger_dialog.exec() == QDialog::Accepted) + { + m_user_triggers.insert_or_assign(trigger_dialog.GetName().toStdString(), + trigger_dialog.GetTrigger()); + ReloadTriggerList(); + } +} + +void GraphicsTriggerWidget::AddTextureLoadTrigger() +{ + NewTextureLoadTriggerDialog trigger_dialog(this); + if (trigger_dialog.exec() == QDialog::Accepted) + { + m_user_triggers.insert_or_assign(trigger_dialog.GetName().toStdString(), + trigger_dialog.GetTrigger()); + ReloadTriggerList(); + } +} diff --git a/Source/Core/DolphinQt/Config/Graphics/Trigger/GraphicsTriggerWidget.h b/Source/Core/DolphinQt/Config/Graphics/Trigger/GraphicsTriggerWidget.h new file mode 100644 index 000000000000..1ca7e0be7bb6 --- /dev/null +++ b/Source/Core/DolphinQt/Config/Graphics/Trigger/GraphicsTriggerWidget.h @@ -0,0 +1,40 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include + +#include "VideoCommon/GraphicsTrigger.h" + +class QListWidget; +class QMenu; +class QPushButton; +class QToolButton; + +class GraphicsTriggerWidget final : public QWidget +{ + Q_OBJECT +public: + explicit GraphicsTriggerWidget(QWidget* parent); + + void LoadSettings(); + void SaveSettings(); +private: + void CreateLayout(); + + void ReloadTriggerList(); + + void AddEFBTrigger(); + void Add2DDrawCallTrigger(); + void Add3DDrawCallTrigger(); + void AddTextureLoadTrigger(); + + QListWidget* m_triggers; + QToolButton* m_add_trigger; + QMenu* m_add_trigger_menu; + QPushButton* m_remove_trigger; + + std::map m_user_triggers; +}; diff --git a/Source/Core/DolphinQt/Config/Graphics/Trigger/GraphicsTriggerWindow.cpp b/Source/Core/DolphinQt/Config/Graphics/Trigger/GraphicsTriggerWindow.cpp new file mode 100644 index 000000000000..eda154d4ee64 --- /dev/null +++ b/Source/Core/DolphinQt/Config/Graphics/Trigger/GraphicsTriggerWindow.cpp @@ -0,0 +1,37 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "DolphinQt/Config/Graphics/Trigger/GraphicsTriggerWindow.h" + +#include "DolphinQt/Config/Graphics/Trigger/GraphicsTriggerWidget.h" + +#include +#include + +GraphicsTriggerWindow::GraphicsTriggerWindow(QWidget* parent) + : QDialog(parent) +{ + CreateMainLayout(); + + setWindowTitle(tr("Graphics Trigger Configuration")); + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); +} + +void GraphicsTriggerWindow::CreateMainLayout() +{ + m_button_box = new QDialogButtonBox(QDialogButtonBox::Close); + connect(m_button_box, &QDialogButtonBox::accepted, this, [this]() { + m_graphics_trigger_widget->SaveSettings(); + accept(); + }); + connect(m_button_box, &QDialogButtonBox::rejected, this, &QDialog::reject); + + auto* main_layout = new QVBoxLayout(); + + m_graphics_trigger_widget = new GraphicsTriggerWidget(this); + main_layout->addWidget(m_graphics_trigger_widget); + + main_layout->addWidget(m_button_box); + setLayout(main_layout); +} diff --git a/Source/Core/DolphinQt/Config/Graphics/Trigger/GraphicsTriggerWindow.h b/Source/Core/DolphinQt/Config/Graphics/Trigger/GraphicsTriggerWindow.h new file mode 100644 index 000000000000..111244fa2857 --- /dev/null +++ b/Source/Core/DolphinQt/Config/Graphics/Trigger/GraphicsTriggerWindow.h @@ -0,0 +1,23 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include + +class GraphicsTriggerWidget; +class QDialogButtonBox; + +class GraphicsTriggerWindow final : public QDialog +{ + Q_OBJECT +public: + explicit GraphicsTriggerWindow(QWidget* parent); + +private: + void CreateMainLayout(); + + GraphicsTriggerWidget* m_graphics_trigger_widget; + QDialogButtonBox* m_button_box; +}; diff --git a/Source/Core/DolphinQt/Config/Graphics/Trigger/NewDrawCall2DTriggerDialog.cpp b/Source/Core/DolphinQt/Config/Graphics/Trigger/NewDrawCall2DTriggerDialog.cpp new file mode 100644 index 000000000000..1186a00cc207 --- /dev/null +++ b/Source/Core/DolphinQt/Config/Graphics/Trigger/NewDrawCall2DTriggerDialog.cpp @@ -0,0 +1,129 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "DolphinQt/Config/Graphics/Trigger/NewDrawCall2DTriggerDialog.h" + +#include + +#include +#include +#include +#include + +#include "DolphinQt/Config/ToolTipControls/ToolTipComboBox.h" +#include "DolphinQt/Config/ToolTipControls/ToolTipLineEdit.h" +#include "DolphinQt/Config/ToolTipControls/ToolTipSpinBox.h" + +NewDrawCall2DTriggerDialog::NewDrawCall2DTriggerDialog(QWidget* parent) : QDialog(parent) +{ + CreateMainLayout(); + + setWindowTitle(tr("New DrawCall2D Trigger")); + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); +} + +const DrawCall2DGraphicsTrigger& NewDrawCall2DTriggerDialog::GetTrigger() const +{ + return m_trigger; +} + +QString NewDrawCall2DTriggerDialog::GetName() const +{ + return m_name->text(); +} + +void NewDrawCall2DTriggerDialog::CreateMainLayout() +{ + auto* main_layout = new QVBoxLayout(); + + m_button_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + connect(m_button_box, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(m_button_box, &QDialogButtonBox::rejected, this, &QDialog::reject); + + auto form_layout = new QFormLayout; + + m_name = new ToolTipLineEdit; + + m_width = new ToolTipSpinBox; + connect(m_width, qOverload(&QSpinBox::valueChanged), this, + [this](int value) { m_trigger.width = value; }); + m_width_operation = new ToolTipComboBox; + for (u32 i = 0; i < static_cast(NumericOperation::Less_Equal); i++) + { + m_width_operation->addItem(QString::fromStdString(fmt::to_string(NumericOperation(i)))); + } + connect(m_width_operation, QOverload::of(&QComboBox::currentIndexChanged), this, + [this](int value) { + m_trigger.width_operation = static_cast(value); + m_width->setEnabled(m_trigger.width_operation != NumericOperation::Any); + }); + + m_height = new ToolTipSpinBox; + connect(m_width, qOverload(&QSpinBox::valueChanged), this, + [this](int value) { m_trigger.height = value; }); + m_height_operation = new ToolTipComboBox; + for (u32 i = 0; i < static_cast(NumericOperation::Less_Equal); i++) + { + m_height_operation->addItem(QString::fromStdString(fmt::to_string(NumericOperation(i)))); + } + connect(m_height_operation, QOverload::of(&QComboBox::currentIndexChanged), this, + [this](int value) { + m_trigger.height_operation = static_cast(value); + m_height->setEnabled(m_trigger.height_operation != NumericOperation::Any); + }); + + m_format = new ToolTipSpinBox; + m_format->setMinimum(static_cast(TextureFormat::I4)); + m_format->setMaximum(static_cast(TextureFormat::CMPR)); + m_format_operation = new ToolTipComboBox; + for (u32 i = 0; i < static_cast(MultiGenericOperation::Any); i++) + { + m_format_operation->addItem(QString::fromStdString(fmt::to_string(MultiGenericOperation(i)))); + } + connect(m_format_operation, QOverload::of(&QComboBox::currentIndexChanged), this, + [this](int value) { + m_trigger.format_operation = static_cast(value); + m_format->setEnabled(m_trigger.format_operation != MultiGenericOperation::Any); + }); + + m_tlut = new ToolTipLineEdit; + m_tlut_operation = new ToolTipComboBox; + for (u32 i = 0; i < static_cast(GenericOperation::Any); i++) + { + m_tlut_operation->addItem(QString::fromStdString(fmt::to_string(GenericOperation(i)))); + } + connect(m_tlut_operation, QOverload::of(&QComboBox::currentIndexChanged), this, + [this](int value) { + m_trigger.tlut_operation = static_cast(value); + m_tlut->setEnabled(m_trigger.tlut_operation != GenericOperation::Any); + }); + + m_hash = new ToolTipLineEdit; + m_hash_operation = new ToolTipComboBox; + for (u32 i = 0; i < static_cast(GenericOperation::Any); i++) + { + m_hash_operation->addItem(QString::fromStdString(fmt::to_string(GenericOperation(i)))); + } + connect(m_hash_operation, QOverload::of(&QComboBox::currentIndexChanged), this, + [this](int value) { + m_trigger.hash_operation = static_cast(value); + m_hash->setEnabled(m_trigger.hash_operation != GenericOperation::Any); + }); + + form_layout->addRow(tr("Name:"), m_name); + form_layout->addRow(tr("Width:"), m_width); + form_layout->addRow(tr("Width Operation:"), m_width_operation); + form_layout->addRow(tr("Height:"), m_height); + form_layout->addRow(tr("Height Operation:"), m_height_operation); + form_layout->addRow(tr("Format:"), m_format); + form_layout->addRow(tr("Format Operation:"), m_format_operation); + form_layout->addRow(tr("Tlut:"), m_tlut); + form_layout->addRow(tr("Tlut Operation:"), m_tlut_operation); + form_layout->addRow(tr("Hash:"), m_hash); + form_layout->addRow(tr("Hash Operation:"), m_hash_operation); + + main_layout->addLayout(form_layout); + main_layout->addWidget(m_button_box); + setLayout(main_layout); +} diff --git a/Source/Core/DolphinQt/Config/Graphics/Trigger/NewDrawCall2DTriggerDialog.h b/Source/Core/DolphinQt/Config/Graphics/Trigger/NewDrawCall2DTriggerDialog.h new file mode 100644 index 000000000000..12673b939cd0 --- /dev/null +++ b/Source/Core/DolphinQt/Config/Graphics/Trigger/NewDrawCall2DTriggerDialog.h @@ -0,0 +1,48 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include + +#include "Common/CommonTypes.h" +#include "VideoCommon/GraphicsTrigger.h" + +class QDialogButtonBox; +class ToolTipComboBox; +class ToolTipLineEdit; +class ToolTipSpinBox; + +class NewDrawCall2DTriggerDialog final : public QDialog +{ + Q_OBJECT +public: + explicit NewDrawCall2DTriggerDialog(QWidget* parent); + + const DrawCall2DGraphicsTrigger& GetTrigger() const; + QString GetName() const; + +private: + void CreateMainLayout(); + ToolTipLineEdit* m_name; + + ToolTipSpinBox* m_width; + ToolTipComboBox* m_width_operation; + + ToolTipSpinBox* m_height; + ToolTipComboBox* m_height_operation; + + ToolTipSpinBox* m_format; + ToolTipComboBox* m_format_operation; + + ToolTipLineEdit* m_tlut; + ToolTipComboBox* m_tlut_operation; + + ToolTipLineEdit* m_hash; + ToolTipComboBox* m_hash_operation; + + QDialogButtonBox* m_button_box; + + DrawCall2DGraphicsTrigger m_trigger; +}; diff --git a/Source/Core/DolphinQt/Config/Graphics/Trigger/NewDrawCall3DTriggerDialog.cpp b/Source/Core/DolphinQt/Config/Graphics/Trigger/NewDrawCall3DTriggerDialog.cpp new file mode 100644 index 000000000000..912dec1aba51 --- /dev/null +++ b/Source/Core/DolphinQt/Config/Graphics/Trigger/NewDrawCall3DTriggerDialog.cpp @@ -0,0 +1,129 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "DolphinQt/Config/Graphics/Trigger/NewDrawCall3DTriggerDialog.h" + +#include + +#include +#include +#include +#include + +#include "DolphinQt/Config/ToolTipControls/ToolTipComboBox.h" +#include "DolphinQt/Config/ToolTipControls/ToolTipLineEdit.h" +#include "DolphinQt/Config/ToolTipControls/ToolTipSpinBox.h" + +NewDrawCall3DTriggerDialog::NewDrawCall3DTriggerDialog(QWidget* parent) : QDialog(parent) +{ + CreateMainLayout(); + + setWindowTitle(tr("New DrawCall3D Trigger")); + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); +} + +const DrawCall3DGraphicsTrigger& NewDrawCall3DTriggerDialog::GetTrigger() const +{ + return m_trigger; +} + +QString NewDrawCall3DTriggerDialog::GetName() const +{ + return m_name->text(); +} + +void NewDrawCall3DTriggerDialog::CreateMainLayout() +{ + auto* main_layout = new QVBoxLayout(); + + m_button_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + connect(m_button_box, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(m_button_box, &QDialogButtonBox::rejected, this, &QDialog::reject); + + auto form_layout = new QFormLayout; + + m_name = new ToolTipLineEdit; + + m_width = new ToolTipSpinBox; + connect(m_width, qOverload(&QSpinBox::valueChanged), this, + [this](int value) { m_trigger.width = value; }); + m_width_operation = new ToolTipComboBox; + for (u32 i = 0; i < static_cast(NumericOperation::Less_Equal); i++) + { + m_width_operation->addItem(QString::fromStdString(fmt::to_string(NumericOperation(i)))); + } + connect(m_width_operation, QOverload::of(&QComboBox::currentIndexChanged), this, + [this](int value) { + m_trigger.width_operation = static_cast(value); + m_width->setEnabled(m_trigger.width_operation != NumericOperation::Any); + }); + + m_height = new ToolTipSpinBox; + connect(m_width, qOverload(&QSpinBox::valueChanged), this, + [this](int value) { m_trigger.height = value; }); + m_height_operation = new ToolTipComboBox; + for (u32 i = 0; i < static_cast(NumericOperation::Less_Equal); i++) + { + m_height_operation->addItem(QString::fromStdString(fmt::to_string(NumericOperation(i)))); + } + connect(m_height_operation, QOverload::of(&QComboBox::currentIndexChanged), this, + [this](int value) { + m_trigger.height_operation = static_cast(value); + m_height->setEnabled(m_trigger.height_operation != NumericOperation::Any); + }); + + m_format = new ToolTipSpinBox; + m_format->setMinimum(static_cast(TextureFormat::I4)); + m_format->setMaximum(static_cast(TextureFormat::CMPR)); + m_format_operation = new ToolTipComboBox; + for (u32 i = 0; i < static_cast(MultiGenericOperation::Any); i++) + { + m_format_operation->addItem(QString::fromStdString(fmt::to_string(MultiGenericOperation(i)))); + } + connect(m_format_operation, QOverload::of(&QComboBox::currentIndexChanged), this, + [this](int value) { + m_trigger.format_operation = static_cast(value); + m_format->setEnabled(m_trigger.format_operation != MultiGenericOperation::Any); + }); + + m_tlut = new ToolTipLineEdit; + m_tlut_operation = new ToolTipComboBox; + for (u32 i = 0; i < static_cast(GenericOperation::Any); i++) + { + m_tlut_operation->addItem(QString::fromStdString(fmt::to_string(GenericOperation(i)))); + } + connect(m_tlut_operation, QOverload::of(&QComboBox::currentIndexChanged), this, + [this](int value) { + m_trigger.tlut_operation = static_cast(value); + m_tlut->setEnabled(m_trigger.tlut_operation != GenericOperation::Any); + }); + + m_hash = new ToolTipLineEdit; + m_hash_operation = new ToolTipComboBox; + for (u32 i = 0; i < static_cast(GenericOperation::Any); i++) + { + m_hash_operation->addItem(QString::fromStdString(fmt::to_string(GenericOperation(i)))); + } + connect(m_hash_operation, QOverload::of(&QComboBox::currentIndexChanged), this, + [this](int value) { + m_trigger.hash_operation = static_cast(value); + m_hash->setEnabled(m_trigger.hash_operation != GenericOperation::Any); + }); + + form_layout->addRow(tr("Name:"), m_name); + form_layout->addRow(tr("Width:"), m_width); + form_layout->addRow(tr("Width Operation:"), m_width_operation); + form_layout->addRow(tr("Height:"), m_height); + form_layout->addRow(tr("Height Operation:"), m_height_operation); + form_layout->addRow(tr("Format:"), m_format); + form_layout->addRow(tr("Format Operation:"), m_format_operation); + form_layout->addRow(tr("Tlut:"), m_tlut); + form_layout->addRow(tr("Tlut Operation:"), m_tlut_operation); + form_layout->addRow(tr("Hash:"), m_hash); + form_layout->addRow(tr("Hash Operation:"), m_hash_operation); + + main_layout->addLayout(form_layout); + main_layout->addWidget(m_button_box); + setLayout(main_layout); +} diff --git a/Source/Core/DolphinQt/Config/Graphics/Trigger/NewDrawCall3DTriggerDialog.h b/Source/Core/DolphinQt/Config/Graphics/Trigger/NewDrawCall3DTriggerDialog.h new file mode 100644 index 000000000000..344ef8d5d692 --- /dev/null +++ b/Source/Core/DolphinQt/Config/Graphics/Trigger/NewDrawCall3DTriggerDialog.h @@ -0,0 +1,48 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include + +#include "Common/CommonTypes.h" +#include "VideoCommon/GraphicsTrigger.h" + +class QDialogButtonBox; +class ToolTipComboBox; +class ToolTipLineEdit; +class ToolTipSpinBox; + +class NewDrawCall3DTriggerDialog final : public QDialog +{ + Q_OBJECT +public: + explicit NewDrawCall3DTriggerDialog(QWidget* parent); + + const DrawCall3DGraphicsTrigger& GetTrigger() const; + QString GetName() const; + +private: + void CreateMainLayout(); + ToolTipLineEdit* m_name; + + ToolTipSpinBox* m_width; + ToolTipComboBox* m_width_operation; + + ToolTipSpinBox* m_height; + ToolTipComboBox* m_height_operation; + + ToolTipSpinBox* m_format; + ToolTipComboBox* m_format_operation; + + ToolTipLineEdit* m_tlut; + ToolTipComboBox* m_tlut_operation; + + ToolTipLineEdit* m_hash; + ToolTipComboBox* m_hash_operation; + + QDialogButtonBox* m_button_box; + + DrawCall3DGraphicsTrigger m_trigger; +}; diff --git a/Source/Core/DolphinQt/Config/Graphics/Trigger/NewEFBTriggerDialog.cpp b/Source/Core/DolphinQt/Config/Graphics/Trigger/NewEFBTriggerDialog.cpp new file mode 100644 index 000000000000..cda6407611b7 --- /dev/null +++ b/Source/Core/DolphinQt/Config/Graphics/Trigger/NewEFBTriggerDialog.cpp @@ -0,0 +1,101 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "DolphinQt/Config/Graphics/Trigger/NewEFBTriggerDialog.h" + +#include + +#include +#include +#include +#include + +#include "DolphinQt/Config/ToolTipControls/ToolTipComboBox.h" +#include "DolphinQt/Config/ToolTipControls/ToolTipLineEdit.h" +#include "DolphinQt/Config/ToolTipControls/ToolTipSpinBox.h" + +NewEFBTriggerDialog::NewEFBTriggerDialog(QWidget* parent) : QDialog(parent) +{ + CreateMainLayout(); + + setWindowTitle(tr("New EFB Trigger")); + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); +} + +const EFBGraphicsTrigger& NewEFBTriggerDialog::GetTrigger() const +{ + return m_trigger; +} + +QString NewEFBTriggerDialog::GetName() const +{ + return m_name->text(); +} + +void NewEFBTriggerDialog::CreateMainLayout() +{ + auto* main_layout = new QVBoxLayout(); + + m_button_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + connect(m_button_box, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(m_button_box, &QDialogButtonBox::rejected, this, &QDialog::reject); + + auto form_layout = new QFormLayout; + + m_name = new ToolTipLineEdit; + + m_width = new ToolTipSpinBox; + connect(m_width, qOverload(&QSpinBox::valueChanged), this, + [this](int value) { m_trigger.width = value; }); + m_width_operation = new ToolTipComboBox; + for (u32 i = 0; i < static_cast(NumericOperation::Less_Equal); i++) + { + m_width_operation->addItem(QString::fromStdString(fmt::to_string(NumericOperation(i)))); + } + connect(m_width_operation, QOverload::of(&QComboBox::currentIndexChanged), this, + [this](int value) { + m_trigger.width_operation = static_cast(value); + m_width->setEnabled(m_trigger.width_operation != NumericOperation::Any); + }); + + m_height = new ToolTipSpinBox; + connect(m_width, qOverload(&QSpinBox::valueChanged), this, + [this](int value) { m_trigger.height = value; }); + m_height_operation = new ToolTipComboBox; + for (u32 i = 0; i < static_cast(NumericOperation::Less_Equal); i++) + { + m_height_operation->addItem(QString::fromStdString(fmt::to_string(NumericOperation(i)))); + } + connect(m_height_operation, QOverload::of(&QComboBox::currentIndexChanged), this, + [this](int value) { + m_trigger.height_operation = static_cast(value); + m_height->setEnabled(m_trigger.height_operation != NumericOperation::Any); + }); + + m_format = new ToolTipSpinBox; + m_format->setMinimum(static_cast(TextureFormat::I4)); + m_format->setMaximum(static_cast(TextureFormat::CMPR)); + m_format_operation = new ToolTipComboBox; + for (u32 i = 0; i < static_cast(MultiGenericOperation::Any); i++) + { + m_format_operation->addItem(QString::fromStdString(fmt::to_string(MultiGenericOperation(i)))); + } + connect(m_format_operation, QOverload::of(&QComboBox::currentIndexChanged), this, + [this](int value) { + m_trigger.format_operation = static_cast(value); + m_format->setEnabled(m_trigger.format_operation != MultiGenericOperation::Any); + }); + + form_layout->addRow(tr("Name:"), m_name); + form_layout->addRow(tr("Width:"), m_width); + form_layout->addRow(tr("Width Operation:"), m_width_operation); + form_layout->addRow(tr("Height:"), m_height); + form_layout->addRow(tr("Height Operation:"), m_height_operation); + form_layout->addRow(tr("Format:"), m_format); + form_layout->addRow(tr("Format Operation:"), m_format_operation); + + main_layout->addLayout(form_layout); + main_layout->addWidget(m_button_box); + setLayout(main_layout); +} diff --git a/Source/Core/DolphinQt/Config/Graphics/Trigger/NewEFBTriggerDialog.h b/Source/Core/DolphinQt/Config/Graphics/Trigger/NewEFBTriggerDialog.h new file mode 100644 index 000000000000..52cf6c0e91df --- /dev/null +++ b/Source/Core/DolphinQt/Config/Graphics/Trigger/NewEFBTriggerDialog.h @@ -0,0 +1,42 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include + +#include "Common/CommonTypes.h" +#include "VideoCommon/GraphicsTrigger.h" + +class QDialogButtonBox; +class ToolTipComboBox; +class ToolTipLineEdit; +class ToolTipSpinBox; + +class NewEFBTriggerDialog final : public QDialog +{ + Q_OBJECT +public: + explicit NewEFBTriggerDialog(QWidget* parent); + + const EFBGraphicsTrigger& GetTrigger() const; + QString GetName() const; + +private: + void CreateMainLayout(); + ToolTipLineEdit* m_name; + + ToolTipSpinBox* m_width; + ToolTipComboBox* m_width_operation; + + ToolTipSpinBox* m_height; + ToolTipComboBox* m_height_operation; + + ToolTipSpinBox* m_format; + ToolTipComboBox* m_format_operation; + + QDialogButtonBox* m_button_box; + + EFBGraphicsTrigger m_trigger; +}; diff --git a/Source/Core/DolphinQt/Config/Graphics/Trigger/NewTextureLoadTriggerDialog.cpp b/Source/Core/DolphinQt/Config/Graphics/Trigger/NewTextureLoadTriggerDialog.cpp new file mode 100644 index 000000000000..8848e3f88a7e --- /dev/null +++ b/Source/Core/DolphinQt/Config/Graphics/Trigger/NewTextureLoadTriggerDialog.cpp @@ -0,0 +1,56 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "DolphinQt/Config/Graphics/Trigger/NewTextureLoadTriggerDialog.h" + +#include + +#include +#include +#include +#include + +#include "DolphinQt/Config/ToolTipControls/ToolTipLineEdit.h" + +NewTextureLoadTriggerDialog::NewTextureLoadTriggerDialog(QWidget* parent) : QDialog(parent) +{ + CreateMainLayout(); + + setWindowTitle(tr("New Texture Load Trigger")); + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); +} + +const TextureLoadGraphicsTrigger& NewTextureLoadTriggerDialog::GetTrigger() const +{ + return m_trigger; +} + +QString NewTextureLoadTriggerDialog::GetName() const +{ + return m_name->text(); +} + +void NewTextureLoadTriggerDialog::CreateMainLayout() +{ + auto* main_layout = new QVBoxLayout(); + + m_button_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + connect(m_button_box, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(m_button_box, &QDialogButtonBox::rejected, this, &QDialog::reject); + + auto form_layout = new QFormLayout; + + m_name = new ToolTipLineEdit; + + m_texture_id = new ToolTipLineEdit; + connect(m_texture_id, &QLineEdit::textChanged, this, + [this](const QString& value) { m_trigger.texture_id = value.toStdString(); }); + + form_layout->addRow(tr("Name:"), m_name); + form_layout->addRow(tr("Texture id:"), m_texture_id); + + main_layout->addLayout(form_layout); + main_layout->addWidget(m_button_box); + setLayout(main_layout); +} diff --git a/Source/Core/DolphinQt/Config/Graphics/Trigger/NewTextureLoadTriggerDialog.h b/Source/Core/DolphinQt/Config/Graphics/Trigger/NewTextureLoadTriggerDialog.h new file mode 100644 index 000000000000..575a488ede0b --- /dev/null +++ b/Source/Core/DolphinQt/Config/Graphics/Trigger/NewTextureLoadTriggerDialog.h @@ -0,0 +1,34 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include + +#include "Common/CommonTypes.h" +#include "VideoCommon/GraphicsTrigger.h" + +class QDialogButtonBox; +class ToolTipLineEdit; + +class NewTextureLoadTriggerDialog final : public QDialog +{ + Q_OBJECT +public: + explicit NewTextureLoadTriggerDialog(QWidget* parent); + + const TextureLoadGraphicsTrigger& GetTrigger() const; + QString GetName() const; + +private: + void CreateMainLayout(); + + ToolTipLineEdit* m_name; + + ToolTipLineEdit* m_texture_id; + + QDialogButtonBox* m_button_box; + + TextureLoadGraphicsTrigger m_trigger; +}; diff --git a/Source/Core/DolphinQt/Config/ToolTipControls/ToolTipLineEdit.cpp b/Source/Core/DolphinQt/Config/ToolTipControls/ToolTipLineEdit.cpp new file mode 100644 index 000000000000..be02843bb2d1 --- /dev/null +++ b/Source/Core/DolphinQt/Config/ToolTipControls/ToolTipLineEdit.cpp @@ -0,0 +1,10 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "DolphinQt/Config/ToolTipControls/ToolTipLineEdit.h" + +QPoint ToolTipLineEdit::GetToolTipPosition() const +{ + return pos() + QPoint(width() / 2, height() / 2); +} diff --git a/Source/Core/DolphinQt/Config/ToolTipControls/ToolTipLineEdit.h b/Source/Core/DolphinQt/Config/ToolTipControls/ToolTipLineEdit.h new file mode 100644 index 000000000000..1d1e807c2ddf --- /dev/null +++ b/Source/Core/DolphinQt/Config/ToolTipControls/ToolTipLineEdit.h @@ -0,0 +1,15 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include "DolphinQt/Config/ToolTipControls/ToolTipWidget.h" + +#include + +class ToolTipLineEdit : public ToolTipWidget +{ +private: + QPoint GetToolTipPosition() const override; +}; diff --git a/Source/Core/DolphinQt/Config/ToolTipControls/ToolTipPushButton.cpp b/Source/Core/DolphinQt/Config/ToolTipControls/ToolTipPushButton.cpp new file mode 100644 index 000000000000..540673d7d4a9 --- /dev/null +++ b/Source/Core/DolphinQt/Config/ToolTipControls/ToolTipPushButton.cpp @@ -0,0 +1,10 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "DolphinQt/Config/ToolTipControls/ToolTipPushButton.h" + +QPoint ToolTipPushButton::GetToolTipPosition() const +{ + return pos() + QPoint(width() / 2, height() / 2); +} diff --git a/Source/Core/DolphinQt/Config/ToolTipControls/ToolTipPushButton.h b/Source/Core/DolphinQt/Config/ToolTipControls/ToolTipPushButton.h new file mode 100644 index 000000000000..d2695abfcc9b --- /dev/null +++ b/Source/Core/DolphinQt/Config/ToolTipControls/ToolTipPushButton.h @@ -0,0 +1,17 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include "DolphinQt/Config/ToolTipControls/ToolTipWidget.h" + +#include + +class ToolTipPushButton : public ToolTipWidget +{ +public: + using ToolTipWidget::ToolTipWidget; +private: + QPoint GetToolTipPosition() const override; +}; diff --git a/Source/Core/DolphinQt/DolphinQt.vcxproj b/Source/Core/DolphinQt/DolphinQt.vcxproj index c8c0061bbea4..8d61fa65a623 100644 --- a/Source/Core/DolphinQt/DolphinQt.vcxproj +++ b/Source/Core/DolphinQt/DolphinQt.vcxproj @@ -67,17 +67,29 @@ + + + - + + + + + + + + + + @@ -117,6 +129,8 @@ + + @@ -250,18 +264,30 @@ + + + - + + + + + + + + + + @@ -296,6 +322,8 @@ + + diff --git a/Source/Core/DolphinQt/MainWindow.cpp b/Source/Core/DolphinQt/MainWindow.cpp index f7f3f08d4658..9bb13f84d3db 100644 --- a/Source/Core/DolphinQt/MainWindow.cpp +++ b/Source/Core/DolphinQt/MainWindow.cpp @@ -209,6 +209,7 @@ MainWindow::MainWindow(std::unique_ptr boot_parameters, setAttribute(Qt::WA_NativeWindow); InitControllers(); + g_Config.LoadDefaultCustomShaders(); CreateComponents(); @@ -294,6 +295,7 @@ MainWindow::~MainWindow() } ShutdownControllers(); + g_Config.SaveDefaultCustomShaders(); QSettings& settings = Settings::GetQSettings(); diff --git a/Source/Core/VideoBackends/D3D/D3DMain.cpp b/Source/Core/VideoBackends/D3D/D3DMain.cpp index dcbdb6661694..dd9f87a3a047 100644 --- a/Source/Core/VideoBackends/D3D/D3DMain.cpp +++ b/Source/Core/VideoBackends/D3D/D3DMain.cpp @@ -80,7 +80,7 @@ void VideoBackend::FillBackendInfo() g_Config.backend_info.bSupportsPrimitiveRestart = true; g_Config.backend_info.bSupportsOversizedViewports = false; g_Config.backend_info.bSupportsGeometryShaders = true; - g_Config.backend_info.bSupportsComputeShaders = false; + g_Config.backend_info.bSupportsComputeShaders = true; g_Config.backend_info.bSupports3DVision = true; g_Config.backend_info.bSupportsPostProcessing = true; g_Config.backend_info.bSupportsPaletteConversion = true; @@ -127,6 +127,7 @@ void VideoBackend::FillBackendInfo() g_Config.backend_info.bSupportsFragmentStoresAndAtomics = shader_model_5_supported; g_Config.backend_info.bSupportsGSInstancing = shader_model_5_supported; g_Config.backend_info.bSupportsSSAA = shader_model_5_supported; + g_Config.backend_info.bSupportsComputeShaders = shader_model_5_supported; g_Config.backend_info.bSupportsGPUTextureDecoding = shader_model_5_supported; } } diff --git a/Source/Core/VideoBackends/D3D/D3DRender.cpp b/Source/Core/VideoBackends/D3D/D3DRender.cpp index 129465c114a0..41db0a9c7792 100644 --- a/Source/Core/VideoBackends/D3D/D3DRender.cpp +++ b/Source/Core/VideoBackends/D3D/D3DRender.cpp @@ -157,6 +157,7 @@ void Renderer::DispatchComputeShader(const AbstractShader* shader, u32 groups_x, D3D::stateman->SetComputeShader(static_cast(shader)->GetD3DComputeShader()); D3D::stateman->SyncComputeBindings(); D3D::context->Dispatch(groups_x, groups_y, groups_z); + D3D::stateman->SetComputeUAV(nullptr); } void Renderer::BindBackbuffer(const ClearColor& clear_color) diff --git a/Source/Core/VideoBackends/D3D12/D3D12PerfQuery.cpp b/Source/Core/VideoBackends/D3D12/D3D12PerfQuery.cpp index dcda278e6657..2386d2f905b4 100644 --- a/Source/Core/VideoBackends/D3D12/D3D12PerfQuery.cpp +++ b/Source/Core/VideoBackends/D3D12/D3D12PerfQuery.cpp @@ -142,6 +142,9 @@ bool PerfQuery::IsFlushed() const void PerfQuery::ResolveQueries() { + if (m_unresolved_queries == 0) + return; + // Do we need to split the resolve as it's wrapping around? if ((m_query_resolve_pos + m_unresolved_queries) > PERF_QUERY_BUFFER_SIZE) ResolveQueries(PERF_QUERY_BUFFER_SIZE - m_query_resolve_pos); diff --git a/Source/Core/VideoBackends/D3D12/DX12Shader.cpp b/Source/Core/VideoBackends/D3D12/DX12Shader.cpp index 39589b6b24bc..df5835f27bd2 100644 --- a/Source/Core/VideoBackends/D3D12/DX12Shader.cpp +++ b/Source/Core/VideoBackends/D3D12/DX12Shader.cpp @@ -13,10 +13,6 @@ namespace DX12 DXShader::DXShader(ShaderStage stage, BinaryData bytecode, std::string_view name) : D3DCommon::Shader(stage, std::move(bytecode)), m_name(UTF8ToWString(name)) { - if (!m_name.empty()) - { - m_compute_pipeline->SetName(m_name.c_str()); - } } DXShader::~DXShader() = default; @@ -56,7 +52,15 @@ bool DXShader::CreateComputePipeline() HRESULT hr = g_dx_context->GetDevice()->CreateComputePipelineState( &desc, IID_PPV_ARGS(&m_compute_pipeline)); CHECK(SUCCEEDED(hr), "Creating compute pipeline failed"); - return SUCCEEDED(hr); + if (SUCCEEDED(hr)) + { + if (!m_name.empty()) + { + m_compute_pipeline->SetName(m_name.c_str()); + } + return true; + } + return false; } } // namespace DX12 diff --git a/Source/Core/VideoBackends/OGL/OGLRender.cpp b/Source/Core/VideoBackends/OGL/OGLRender.cpp index 5b4f4f53eb6c..71feb75ccaf4 100644 --- a/Source/Core/VideoBackends/OGL/OGLRender.cpp +++ b/Source/Core/VideoBackends/OGL/OGLRender.cpp @@ -970,10 +970,10 @@ void Renderer::RenderXFBToScreen(const MathUtil::Rectangle& target_rc, return ::Renderer::RenderXFBToScreen(target_rc, source_texture, source_rc); glDrawBuffer(GL_BACK_LEFT); - m_post_processor->BlitFromTexture(target_rc, source_rc, source_texture, 0); + // TODO: post process glDrawBuffer(GL_BACK_RIGHT); - m_post_processor->BlitFromTexture(target_rc, source_rc, source_texture, 1); + // TODO: post process glDrawBuffer(GL_BACK); } diff --git a/Source/Core/VideoCommon/CMakeLists.txt b/Source/Core/VideoCommon/CMakeLists.txt index d9fe3ff1f96b..dfa2dd7d08f0 100644 --- a/Source/Core/VideoCommon/CMakeLists.txt +++ b/Source/Core/VideoCommon/CMakeLists.txt @@ -1,4 +1,39 @@ add_library(videocommon + PEShaderSystem/Config/PEShaderConfig.cpp + PEShaderSystem/Config/PEShaderConfig.h + PEShaderSystem/Config/PEShaderConfigGroup.cpp + PEShaderSystem/Config/PEShaderConfigGroup.h + PEShaderSystem/Config/PEShaderConfigInput.cpp + PEShaderSystem/Config/PEShaderConfigInput.h + PEShaderSystem/Config/PEShaderConfigOption.cpp + PEShaderSystem/Config/PEShaderConfigOption.h + PEShaderSystem/Config/PEShaderConfigPass.cpp + PEShaderSystem/Config/PEShaderConfigPass.h + PEShaderSystem/Config/PETriggerConfig.cpp + PEShaderSystem/Config/PETriggerConfig.h + PEShaderSystem/Constants.h + PEShaderSystem/Runtime/BuiltinUniforms.cpp + PEShaderSystem/Runtime/BuiltinUniforms.h + PEShaderSystem/Runtime/PEBaseShader.cpp + PEShaderSystem/Runtime/PEBaseShader.h + PEShaderSystem/Runtime/PEBaseShaderPass.cpp + PEShaderSystem/Runtime/PEBaseShaderPass.h + PEShaderSystem/Runtime/PEComputeShader.cpp + PEShaderSystem/Runtime/PEComputeShader.h + PEShaderSystem/Runtime/PEComputeShaderPass.h + PEShaderSystem/Runtime/PEShader.cpp + PEShaderSystem/Runtime/PEShader.h + PEShaderSystem/Runtime/PEShaderApplyOptions.h + PEShaderSystem/Runtime/PEShaderGroup.cpp + PEShaderSystem/Runtime/PEShaderGroup.h + PEShaderSystem/Runtime/PEShaderInput.cpp + PEShaderSystem/Runtime/PEShaderInput.h + PEShaderSystem/Runtime/PEShaderOption.cpp + PEShaderSystem/Runtime/PEShaderOption.h + PEShaderSystem/Runtime/PEShaderPass.h + PEShaderSystem/Runtime/PETriggerPointManager.cpp + PEShaderSystem/Runtime/PETriggerPointManager.h + PEShaderSystem/Runtime/RuntimeShaderImpl.h AbstractFramebuffer.cpp AbstractFramebuffer.h AbstractShader.h @@ -39,6 +74,10 @@ add_library(videocommon GeometryShaderGen.h GeometryShaderManager.cpp GeometryShaderManager.h + GraphicsTrigger.cpp + GraphicsTrigger.h + GraphicsTriggerManager.cpp + GraphicsTriggerManager.h HiresTextures.cpp HiresTextures.h HiresTextures_DDSLoader.cpp @@ -56,6 +95,36 @@ add_library(videocommon OnScreenDisplay.h OpcodeDecoding.cpp OpcodeDecoding.h + PEShaderSystem/Config/PEShaderConfig.cpp + PEShaderSystem/Config/PEShaderConfig.h + PEShaderSystem/Config/PEShaderConfigGroup.cpp + PEShaderSystem/Config/PEShaderConfigGroup.h + PEShaderSystem/Config/PEShaderConfigInput.cpp + PEShaderSystem/Config/PEShaderConfigInput.h + PEShaderSystem/Config/PEShaderConfigOption.cpp + PEShaderSystem/Config/PEShaderConfigOption.h + PEShaderSystem/Config/PEShaderConfigPass.cpp + PEShaderSystem/Config/PEShaderConfigPass.h + PEShaderSystem/Config/PETriggerConfig.cpp + PEShaderSystem/Config/PETriggerConfig.h + PEShaderSystem/Runtime/BuiltinUniforms.cpp + PEShaderSystem/Runtime/BuiltinUniforms.h + PEShaderSystem/Runtime/PEBaseShader.cpp + PEShaderSystem/Runtime/PEBaseShader.h + PEShaderSystem/Runtime/PEBaseShaderPass.cpp + PEShaderSystem/Runtime/PEBaseShaderPass.h + PEShaderSystem/Runtime/PEComputeShader.cpp + PEShaderSystem/Runtime/PEComputeShader.h + PEShaderSystem/Runtime/PEShader.cpp + PEShaderSystem/Runtime/PEShader.h + PEShaderSystem/Runtime/PEShaderGroup.cpp + PEShaderSystem/Runtime/PEShaderGroup.h + PEShaderSystem/Runtime/PEShaderInput.cpp + PEShaderSystem/Runtime/PEShaderInput.h + PEShaderSystem/Runtime/PEShaderOption.cpp + PEShaderSystem/Runtime/PEShaderOption.h + PEShaderSystem/Runtime/PETriggerPointManager.cpp + PEShaderSystem/Runtime/PETriggerPointManager.h PerfQueryBase.cpp PerfQueryBase.h PixelEngine.cpp @@ -64,8 +133,6 @@ add_library(videocommon PixelShaderGen.h PixelShaderManager.cpp PixelShaderManager.h - PostProcessing.cpp - PostProcessing.h RenderBase.cpp RenderBase.h RenderState.cpp diff --git a/Source/Core/VideoCommon/GraphicsTrigger.cpp b/Source/Core/VideoCommon/GraphicsTrigger.cpp new file mode 100644 index 000000000000..1de3089852db --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsTrigger.cpp @@ -0,0 +1,208 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "VideoCommon/GraphicsTrigger.h" + +#include + +#include "Common/IniFile.h" +#include "Common/VariantUtil.h" + +bool LoadFromFile(GraphicsTrigger& trigger, const std::string& file) +{ + IniFile ini_file; + ini_file.Load(file); + IniFile::Section* section = ini_file.GetOrCreateSection("Trigger"); + + std::string type; + if (!section->Get("Type", &type)) + return false; + + const auto load_width_height_format = [&](auto& trigger) -> bool { + u32 width_operation; + if (!section->Get("WidthOperation", &width_operation)) + return false; + trigger.width_operation = static_cast(width_operation); + if (trigger.width_operation != NumericOperation::Any) + { + if (!section->Get("Width", &trigger.width)) + return false; + } + + u32 height_operation; + if (!section->Get("HeightOperation", &height_operation)) + return false; + trigger.height_operation = static_cast(height_operation); + if (trigger.height_operation != NumericOperation::Any) + { + if (!section->Get("Height", &trigger.height)) + return false; + } + + u32 format_operation; + if (!section->Get("FormatOperation", &format_operation)) + return false; + trigger.format_operation = static_cast(format_operation); + if (trigger.format_operation != MultiGenericOperation::Any) + { + if (trigger.format_operation == MultiGenericOperation::Exact) + { + TextureFormat format; + if (!section->Get("Format", &format)) + return false; + trigger.formats.push_back(format); + } + else + { + std::string delimited_format; + if (!section->Get("Format", &delimited_format)) + return false; + const auto formats = SplitString(delimited_format, ','); + for (const auto& format_str : formats) + { + TextureFormat format; + if (!TryParse(format_str, &format)) + { + return false; + } + trigger.formats.push_back(format); + } + } + } + + return true; + }; + + const auto load_hash_tlut = [&](auto& trigger) -> bool { + u32 tlut_operation; + if (!section->Get("TlutOperation", &tlut_operation)) + return false; + trigger.tlut_operation = static_cast(tlut_operation); + if (trigger.tlut_operation != GenericOperation::Any) + { + if (!section->Get("Tlut", &trigger.tlut)) + return false; + } + + u32 hash_operation; + if (!section->Get("HashOperation", &hash_operation)) + return false; + trigger.hash_operation = static_cast(hash_operation); + if (trigger.hash_operation != GenericOperation::Any) + { + if (!section->Get("Hash", &trigger.hash)) + return false; + } + + return true; + }; + + if (type == "EFB") + { + EFBGraphicsTrigger efb_trigger; + if (!load_width_height_format(efb_trigger)) + return false; + + trigger = efb_trigger; + return true; + } + else if (type == "DrawCall2D") + { + DrawCall2DGraphicsTrigger drawcall2d_trigger; + if (!load_width_height_format(drawcall2d_trigger)) + return false; + if (!load_hash_tlut(drawcall2d_trigger)) + return false; + + trigger = drawcall2d_trigger; + return true; + } + else if (type == "DrawCall3D") + { + DrawCall3DGraphicsTrigger drawcall3d_trigger; + if (!load_width_height_format(drawcall3d_trigger)) + return false; + if (!load_hash_tlut(drawcall3d_trigger)) + return false; + + trigger = drawcall3d_trigger; + return true; + } + else if (type == "TextureLoad") + { + TextureLoadGraphicsTrigger textureload_trigger; + if (!section->Get("TextureId", &textureload_trigger.texture_id)) + return false; + + trigger = textureload_trigger; + return true; + } + else if (type == "Post") + { + return true; + } + + return false; +} + +void SaveToFile(const GraphicsTrigger& trigger, const std::string& file) +{ + IniFile ini_file; + IniFile::Section* section = ini_file.GetOrCreateSection("Trigger"); + + const auto save_width_height_format = [&](const auto& trigger) { + section->Set("WidthOperation", trigger.width_operation); + if (trigger.width_operation != NumericOperation::Any) + { + section->Set("Width", trigger.width); + } + + section->Set("HeightOperation", trigger.height_operation); + if (trigger.height_operation != NumericOperation::Any) + { + section->Set("Height", trigger.height); + } + + section->Set("FormatOperation", trigger.format_operation); + if (trigger.format_operation != MultiGenericOperation::Any) + { + section->Set("Format", fmt::to_string(fmt::join(trigger.formats, ","))); + } + }; + + const auto save_hash_tlut = [&](const auto& trigger) { + section->Set("HashOperation", trigger.hash_operation); + if (trigger.hash_operation != GenericOperation::Any) + { + section->Set("Hash", trigger.hash); + } + + section->Set("TlutOperation", trigger.tlut_operation); + if (trigger.tlut_operation != GenericOperation::Any) + { + section->Set("Tlut", trigger.tlut); + } + }; + + std::visit(overloaded{[&](const EFBGraphicsTrigger& t) { + section->Set("Type", "EFB"); + save_width_height_format(t); + }, + [&](const TextureLoadGraphicsTrigger& t) { + section->Set("Type", "TextureLoad"); + section->Set("TextureId", t.texture_id); + }, + [&](const DrawCall2DGraphicsTrigger& t) { + section->Set("Type", "DrawCall2D"); + save_width_height_format(t); + save_hash_tlut(t); + }, + [&](const DrawCall3DGraphicsTrigger& t) { + section->Set("Type", "DrawCall3D"); + save_width_height_format(t); + save_hash_tlut(t); + }, + [&](const PostGraphicsTrigger& t) { section->Set("Type", "Post"); }}, + trigger); +} diff --git a/Source/Core/VideoCommon/GraphicsTrigger.h b/Source/Core/VideoCommon/GraphicsTrigger.h new file mode 100644 index 000000000000..a603b13d1127 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsTrigger.h @@ -0,0 +1,123 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include + +#include "Common/CommonTypes.h" +#include "Common/EnumFormatter.h" +#include "VideoCommon/TextureDecoder.h" + +enum class NumericOperation : u32 +{ + Exact = 0, + Any = 1, + Greater = 2, + Greater_Equal = 3, + Less = 4, + Less_Equal = 5 +}; + +template <> +struct fmt::formatter : EnumFormatter +{ + formatter() + : EnumFormatter({"Exact", "Any", "Greater than", "Greater than or equal to", "Less than", + "Less than or equal to"}) + { + } +}; + +enum class GenericOperation : u32 +{ + Exact = 0, + Any = 1 +}; + +template <> +struct fmt::formatter : EnumFormatter +{ + formatter() : EnumFormatter({"Exact", "Any"}) {} +}; + +enum class MultiGenericOperation : u32 +{ + Exact = 0, + Any = 1, + OneOf = 2 +}; + +template <> +struct fmt::formatter : EnumFormatter +{ + formatter() : EnumFormatter({"Exact", "Any", "OneOf"}) {} +}; + +struct EFBGraphicsTrigger +{ + u32 width; + NumericOperation width_operation; + + u32 height; + NumericOperation height_operation; + + std::vector formats; + MultiGenericOperation format_operation; +}; + +struct TextureLoadGraphicsTrigger +{ + std::string texture_id; +}; + +struct DrawCall2DGraphicsTrigger +{ + u32 width; + NumericOperation width_operation; + + u32 height; + NumericOperation height_operation; + + std::vector formats; + MultiGenericOperation format_operation; + + std::string tlut; + GenericOperation tlut_operation; + + std::string hash; + GenericOperation hash_operation; +}; + +struct DrawCall3DGraphicsTrigger +{ + u32 width; + NumericOperation width_operation; + + u32 height; + NumericOperation height_operation; + + std::vector formats; + MultiGenericOperation format_operation; + + std::string tlut; + GenericOperation tlut_operation; + + std::string hash; + GenericOperation hash_operation; +}; + +struct PostGraphicsTrigger +{ +}; + +using GraphicsTrigger = + std::variant; + +bool LoadFromFile(GraphicsTrigger& trigger, const std::string& file); +void SaveToFile(const GraphicsTrigger& trigger, const std::string& file); diff --git a/Source/Core/VideoCommon/GraphicsTriggerManager.cpp b/Source/Core/VideoCommon/GraphicsTriggerManager.cpp new file mode 100644 index 000000000000..e3f68e1ffdf9 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsTriggerManager.cpp @@ -0,0 +1,55 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "VideoCommon/GraphicsTriggerManager.h" + +#include "Common/FileSearch.h" +#include "Common/FileUtil.h" + +const GraphicsTrigger* GraphicsTriggerManager::GetTrigger(const std::string& name) const +{ + if (auto iter = m_user_triggers.find(name); iter != m_user_triggers.end()) + { + return &iter->second; + } + + if (auto iter = m_internal_triggers.find(name); iter != m_internal_triggers.end()) + { + return &iter->second; + } + + return nullptr; +} + +bool GraphicsTriggerManager::Load() +{ + m_user_triggers.clear(); + m_internal_triggers.clear(); + + const std::string profiles_path = File::GetUserPath(D_CONFIG_IDX) + directory_path; + for (const auto& filename : Common::DoFileSearch({profiles_path}, {".ini"})) + { + GraphicsTrigger trigger; + if (!LoadFromFile(trigger, filename)) + return false; + + std::string basename; + SplitPath(filename, nullptr, &basename, nullptr); + m_user_triggers.insert_or_assign(basename, trigger); + } + + const std::string builtin_profiles_path = File::GetSysDirectory() + directory_path; + for (const auto& filename : Common::DoFileSearch({builtin_profiles_path}, {".ini"})) + { + GraphicsTrigger trigger; + if (!LoadFromFile(trigger, filename)) + return false; + + std::string basename; + SplitPath(filename, nullptr, &basename, nullptr); + m_internal_triggers.insert_or_assign(basename, trigger); + } + + return true; +} diff --git a/Source/Core/VideoCommon/GraphicsTriggerManager.h b/Source/Core/VideoCommon/GraphicsTriggerManager.h new file mode 100644 index 000000000000..3a3a8ba82fba --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsTriggerManager.h @@ -0,0 +1,24 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +#include "Common/FileUtil.h" +#include "VideoCommon/GraphicsTrigger.h" + +class GraphicsTriggerManager +{ +public: + const GraphicsTrigger* GetTrigger(const std::string& name) const; + + inline static const std::string directory_path = "Profiles/GraphicsTrigger"; + + bool Load(); +private: + std::map m_user_triggers; + std::map m_internal_triggers; +}; diff --git a/Source/Core/VideoCommon/HiresTextures.cpp b/Source/Core/VideoCommon/HiresTextures.cpp index 85c9cfc23839..9e73b2567692 100644 --- a/Source/Core/VideoCommon/HiresTextures.cpp +++ b/Source/Core/VideoCommon/HiresTextures.cpp @@ -213,7 +213,7 @@ void HiresTexture::Prefetch() 10000); } -std::string HiresTexture::GenBaseName(TextureInfo& texture_info, bool dump) +std::string HiresTexture::GenBaseName(const TextureInfo& texture_info, bool dump) { if (!dump && s_textureMap.empty()) return ""; @@ -261,7 +261,7 @@ u32 HiresTexture::CalculateMipCount(u32 width, u32 height) return mip_count; } -std::shared_ptr HiresTexture::Search(TextureInfo& texture_info) +std::shared_ptr HiresTexture::Search(const TextureInfo& texture_info) { const std::string base_filename = GenBaseName(texture_info); diff --git a/Source/Core/VideoCommon/HiresTextures.h b/Source/Core/VideoCommon/HiresTextures.h index 64e20746f275..37928a727213 100644 --- a/Source/Core/VideoCommon/HiresTextures.h +++ b/Source/Core/VideoCommon/HiresTextures.h @@ -25,9 +25,9 @@ class HiresTexture static void Clear(); static void Shutdown(); - static std::shared_ptr Search(TextureInfo& texture_info); + static std::shared_ptr Search(const TextureInfo& texture_info); - static std::string GenBaseName(TextureInfo& texture_info, bool dump = false); + static std::string GenBaseName(const TextureInfo& texture_info, bool dump = false); static u32 CalculateMipCount(u32 width, u32 height); diff --git a/Source/Core/VideoCommon/PEShaderSystem/Config/PEShaderConfig.cpp b/Source/Core/VideoCommon/PEShaderSystem/Config/PEShaderConfig.cpp new file mode 100644 index 000000000000..05bc151475de --- /dev/null +++ b/Source/Core/VideoCommon/PEShaderSystem/Config/PEShaderConfig.cpp @@ -0,0 +1,333 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "VideoCommon/PEShaderSystem/Config/PEShaderConfig.h" + +#include +#include + +#include +#include + +#include "Common/FileUtil.h" +#include "Common/Logging/Log.h" +#include "VideoCommon/PEShaderSystem/Constants.h" + +namespace +{ +std::string GetStreamAsString(std::ifstream& stream) +{ + std::stringstream ss; + ss << stream.rdbuf(); + return ss.str(); +} +} // namespace + +namespace VideoCommon::PE +{ +bool RuntimeInfo::HasError() const +{ + return m_error.load(); +} + +void RuntimeInfo::SetError(bool error) +{ + m_error.store(error); +} + +std::map> ShaderConfig::GetGroups() +{ + std::map> result; + for (auto& option : m_options) + { + const std::string group = GetOptionGroup(option); + result[group].push_back(&option); + } + return result; +} + +void ShaderConfig::Reset() +{ + for (auto& option : m_options) + { + PE::Reset(option); + } + m_changes += 1; +} + +void ShaderConfig::Reload() +{ + m_compiletime_changes += 1; +} + +void ShaderConfig::LoadSnapshot(std::size_t channel) +{ + for (auto& option : m_options) + { + PE::LoadSnapshot(option, channel); + } + m_changes += 1; +} + +void ShaderConfig::SaveSnapshot(std::size_t channel) +{ + for (auto& option : m_options) + { + PE::SaveSnapshot(option, channel); + } +} + +bool ShaderConfig::HasSnapshot(std::size_t channel) const +{ + return std::any_of(m_options.begin(), m_options.end(), [&](const ShaderConfigOption& option) { + return PE::HasSnapshot(option, channel); + }); +} + +bool ShaderConfig::DeserializeFromConfig(const picojson::value& value) +{ + const auto& meta = value.get("meta"); + if (meta.is()) + { + 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& type = value.get("type"); + if (type.is()) + { + auto type_str = type.to_str(); + std::transform(type_str.begin(), type_str.end(), type_str.begin(), + [](unsigned char c) { return std::tolower(c); }); + if (type_str == "vertex_pixel") + { + m_type = Type::VertexPixel; + } + else if (type_str == "compute") + { + m_type = Type::Compute; + } + else + { + ERROR_LOG_FMT(VIDEO, "Failed to load shader configuration file, 'type' must be either " + "'vertex_pixel' or 'compute'"); + return false; + } + } + else + { + INFO_LOG_FMT(VIDEO, "Missing 'type' or value is not a string. Defaulting to 'vertex_pixel'"); + m_type = Type::VertexPixel; + } + + const auto& options = value.get("options"); + if (options.is()) + { + for (const auto& option_val : options.get()) + { + if (!option_val.is()) + { + ERROR_LOG_FMT( + VIDEO, + "Failed to load shader configuration file, specified option is not a json object"); + return false; + } + const auto option = DeserializeOptionFromConfig(option_val.get()); + if (!option) + { + return false; + } + + m_options.push_back(*option); + } + } + + const auto& passes = value.get("passes"); + if (passes.is()) + { + const auto& passes_arr = passes.get(); + for (std::size_t i = 0; i < passes_arr.size(); i++) + { + const auto& pass_val = passes_arr[i]; + if (!pass_val.is()) + { + ERROR_LOG_FMT( + VIDEO, "Failed to load shader configuration file, specified pass is not a json object"); + return false; + } + ShaderConfigPass pass; + if (!pass.DeserializeFromConfig(pass_val.get(), i, passes_arr.size())) + { + return false; + } + + m_passes.push_back(std::move(pass)); + } + } + + return true; +} + +void ShaderConfig::SerializeToProfile(picojson::object& obj) const +{ + switch (m_source) + { + case Source::Default: + return; + case Source::User: + { + obj["source"] = picojson::value{"user"}; + + const std::string base_path = File::GetUserPath(D_SHADERS_IDX); + obj["path"] = picojson::value{m_shader_path.substr(base_path.size())}; + } + break; + case Source::System: + { + obj["source"] = picojson::value{"system"}; + const std::string base_path = + fmt::format("{}/{}", File::GetSysDirectory(), + VideoCommon::PE::Constants::dolphin_shipped_public_shader_directory); + obj["path"] = picojson::value{m_shader_path.substr(base_path.size())}; + } + break; + }; + + picojson::array serialized_passes; + for (const auto& pass : m_passes) + { + picojson::object serialized_pass; + pass.SerializeToProfile(serialized_pass); + serialized_passes.push_back(picojson::value{serialized_pass}); + } + obj["passes"] = picojson::value{serialized_passes}; + + picojson::array serialized_options; + for (const auto& option : m_options) + { + picojson::object serialized_option; + SerializeOptionToProfile(serialized_option, option); + serialized_options.push_back(picojson::value{serialized_option}); + } + obj["options"] = picojson::value{serialized_options}; +} + +void ShaderConfig::DeserializeFromProfile(const picojson::object& value) +{ + if (const auto it = value.find("passes"); it != value.end()) + { + if (it->second.is()) + { + auto serialized_passes = it->second.get(); + if (serialized_passes.size() != m_passes.size()) + return; + + for (std::size_t i = 0; i < serialized_passes.size(); i++) + { + const auto& serialized_pass_val = serialized_passes[i]; + if (serialized_pass_val.is()) + { + const auto& serialized_pass = serialized_pass_val.get(); + m_passes[i].DeserializeFromProfile(serialized_pass); + } + } + } + } + + if (const auto it = value.find("options"); it != value.end()) + { + if (it->second.is()) + { + auto serialized_options = it->second.get(); + if (serialized_options.size() != m_options.size()) + return; + + for (std::size_t i = 0; i < serialized_options.size(); i++) + { + const auto& serialized_option_val = serialized_options[i]; + if (serialized_option_val.is()) + { + const auto& serialized_option = serialized_option_val.get(); + DeserializeOptionFromProfile(serialized_option, m_options[i]); + } + } + } + } +} + +std::optional ShaderConfig::Load(const std::string& shader_path, Source source) +{ + ShaderConfig result; + result.m_runtime_info = std::make_shared(); + + std::string basename; + std::string base_path; + SplitPath(shader_path, &base_path, &basename, nullptr); + + result.m_name = basename; + result.m_shader_path = shader_path; + result.m_source = source; + + const std::string config_file = fmt::format("{}/{}.json", base_path, basename); + if (File::IsFile(config_file)) + { + std::ifstream json_stream; + File::OpenFStream(json_stream, config_file, std::ios_base::in); + if (!json_stream.is_open()) + { + ERROR_LOG_FMT(VIDEO, "Failed to load shader configuration file '{}'", config_file); + return std::nullopt; + } + + picojson::value root; + const auto error = picojson::parse(root, GetStreamAsString(json_stream)); + + if (!error.empty()) + { + ERROR_LOG_FMT(VIDEO, "Failed to load shader configuration file '{}', due to parse error: {}", + config_file, error); + return std::nullopt; + } + if (!result.DeserializeFromConfig(root)) + { + return std::nullopt; + } + } + else + { + result.m_description = "No description provided"; + result.m_author = "Unknown"; + result.m_type = Type::VertexPixel; + result.m_passes.push_back(ShaderConfigPass::CreateDefaultPass()); + } + + return result; +} + +ShaderConfig ShaderConfig::CreateDefaultShader() +{ + ShaderConfig result; + result.m_name = "Default"; + result.m_description = "Default vertex/pixel shader"; + result.m_type = Type::VertexPixel; + result.m_shader_path = fmt::format("{}/{}/{}", File::GetSysDirectory(), + Constants::dolphin_shipped_internal_shader_directory, + "DefaultVertexPixelShader.glsl"); + result.m_author = "Dolphin"; + result.m_source = Source::Default; + result.m_passes.push_back(ShaderConfigPass::CreateDefaultPass()); + result.m_runtime_info = std::make_shared(); + return result; +} + +} // namespace VideoCommon::PE diff --git a/Source/Core/VideoCommon/PEShaderSystem/Config/PEShaderConfig.h b/Source/Core/VideoCommon/PEShaderSystem/Config/PEShaderConfig.h new file mode 100644 index 000000000000..77ddf57da82a --- /dev/null +++ b/Source/Core/VideoCommon/PEShaderSystem/Config/PEShaderConfig.h @@ -0,0 +1,82 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include + +#include + +#include "Common/CommonTypes.h" + +#include "VideoCommon/PEShaderSystem/Config/PEShaderConfigOption.h" +#include "VideoCommon/PEShaderSystem/Config/PEShaderConfigPass.h" + +namespace VideoCommon::PE +{ +// Note: this class can be accessed by both +// the video thread and the UI +// thread +class RuntimeInfo +{ +public: + bool HasError() const; + void SetError(bool error); + +private: + std::atomic m_error; +}; + +struct ShaderConfig +{ + std::string m_name; + std::string m_author; + std::string m_description; + std::string m_shader_path; + + enum class Source + { + Default, + User, + System + }; + Source m_source; + + enum class Type + { + VertexPixel, + Compute + }; + Type m_type = Type::VertexPixel; + + std::vector m_options; + std::vector m_passes; + bool m_enabled = true; + bool m_requires_depth_buffer = false; + std::shared_ptr m_runtime_info = nullptr; + + u32 m_changes = 0; + u32 m_compiletime_changes = 0; + + // bool IsValid() const; + std::map> GetGroups(); + void Reset(); + void Reload(); + void LoadSnapshot(std::size_t channel); + void SaveSnapshot(std::size_t channel); + bool HasSnapshot(std::size_t channel) const; + + bool DeserializeFromConfig(const picojson::value& value); + + void SerializeToProfile(picojson::object& value) const; + void DeserializeFromProfile(const picojson::object& value); + + static std::optional Load(const std::string& path, Source source); + static ShaderConfig CreateDefaultShader(); +}; +} // namespace VideoCommon::PE diff --git a/Source/Core/VideoCommon/PEShaderSystem/Config/PEShaderConfigGroup.cpp b/Source/Core/VideoCommon/PEShaderSystem/Config/PEShaderConfigGroup.cpp new file mode 100644 index 000000000000..d1cbd48bc6b7 --- /dev/null +++ b/Source/Core/VideoCommon/PEShaderSystem/Config/PEShaderConfigGroup.cpp @@ -0,0 +1,142 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "VideoCommon/PEShaderSystem/Config/PEShaderConfigGroup.h" + +#include + +#include "Common/FileUtil.h" +#include "VideoCommon/PEShaderSystem/Constants.h" + +namespace VideoCommon::PE +{ +bool ShaderConfigGroup::AddShader(const std::string& path, ShaderConfig::Source source) +{ + if (auto shader = ShaderConfig::Load(path, source)) + { + m_shaders.push_back(*shader); + const u32 index = static_cast(m_shaders.size()); + m_shader_order.push_back(index - 1); + m_changes += 1; + return true; + } + + return false; +} + +void ShaderConfigGroup::RemoveShader(u32 index) +{ + auto it = std::find_if(m_shader_order.begin(), m_shader_order.end(), + [index](u32 order_index) { return index == order_index; }); + if (it == m_shader_order.end()) + return; + m_shader_order.erase(it); + + for (std::size_t i = 0; i < m_shader_order.size(); i++) + { + if (m_shader_order[i] > index) + { + m_shader_order[i]--; + } + } + + m_shaders.erase(m_shaders.begin() + index); + m_changes += 1; +} + +void ShaderConfigGroup::SerializeToProfile(picojson::value& value) const +{ + picojson::array serialized_shaders; + for (const auto& shader : m_shaders) + { + picojson::object serialized_shader; + shader.SerializeToProfile(serialized_shader); + serialized_shaders.push_back(picojson::value{serialized_shader}); + } + value = picojson::value{serialized_shaders}; +} + +void ShaderConfigGroup::DeserializeFromProfile(const picojson::array& serialized_shaders) +{ + if (serialized_shaders.empty()) + { + const auto new_group = CreateDefaultGroup(); + m_shaders = new_group.m_shaders; + m_shader_order = new_group.m_shader_order; + m_changes += 1; + return; + } + + std::vector new_group; + for (const auto& serialized_shader : serialized_shaders) + { + if (serialized_shader.is()) + { + const auto serialized_shader_obj = serialized_shader.get(); + + std::string source; + if (const auto it = serialized_shader_obj.find("source"); it != serialized_shader_obj.end()) + { + source = it->second.to_str(); + } + + std::string shader_relative_path; + if (const auto it = serialized_shader_obj.find("path"); it != serialized_shader_obj.end()) + { + shader_relative_path = it->second.to_str(); + } + + if (source == "" || shader_relative_path == "") + { + continue; + } + + if (source == "user") + { + const auto shader_full_path = + fmt::format("{}/{}", File::GetUserPath(D_SHADERS_IDX), shader_relative_path); + if (const auto config = ShaderConfig::Load(shader_full_path, ShaderConfig::Source::User)) + { + new_group.push_back(*config); + new_group.back().DeserializeFromProfile(serialized_shader_obj); + } + } + else if (source == "system") + { + const auto shader_full_path = + fmt::format("{}/{}/{}", File::GetSysDirectory(), + VideoCommon::PE::Constants::dolphin_shipped_public_shader_directory, + shader_relative_path); + if (const auto config = ShaderConfig::Load(shader_full_path, ShaderConfig::Source::System)) + { + new_group.push_back(*config); + new_group.back().DeserializeFromProfile(serialized_shader_obj); + } + } + } + } + + if (!new_group.empty()) + { + m_shaders = new_group; + + m_shader_order.clear(); + for (std::size_t i = 0; i < m_shaders.size(); i++) + { + m_shader_order.push_back(static_cast(i)); + } + + m_changes += 1; + } +} + +ShaderConfigGroup ShaderConfigGroup::CreateDefaultGroup() +{ + ShaderConfigGroup group; + group.m_shaders.push_back(ShaderConfig::CreateDefaultShader()); + group.m_shader_order.push_back(0); + group.m_changes += 1; + return group; +} +} // namespace VideoCommon::PE diff --git a/Source/Core/VideoCommon/PEShaderSystem/Config/PEShaderConfigGroup.h b/Source/Core/VideoCommon/PEShaderSystem/Config/PEShaderConfigGroup.h new file mode 100644 index 000000000000..45465e8b72be --- /dev/null +++ b/Source/Core/VideoCommon/PEShaderSystem/Config/PEShaderConfigGroup.h @@ -0,0 +1,30 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include + +#include + +#include "Common/CommonTypes.h" +#include "VideoCommon/PEShaderSystem/Config/PEShaderConfig.h" + +namespace VideoCommon::PE +{ +struct ShaderConfigGroup +{ + std::vector m_shaders; + std::vector m_shader_order; + u32 m_changes = 0; + + bool AddShader(const std::string& path, ShaderConfig::Source source); + void RemoveShader(u32 index); + + void SerializeToProfile(picojson::value& value) const; + void DeserializeFromProfile(const picojson::array& serialized_shaders); + + static ShaderConfigGroup CreateDefaultGroup(); +}; +} // namespace VideoCommon::PE diff --git a/Source/Core/VideoCommon/PEShaderSystem/Config/PEShaderConfigInput.cpp b/Source/Core/VideoCommon/PEShaderSystem/Config/PEShaderConfigInput.cpp new file mode 100644 index 000000000000..29bde87cccd4 --- /dev/null +++ b/Source/Core/VideoCommon/PEShaderSystem/Config/PEShaderConfigInput.cpp @@ -0,0 +1,231 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "VideoCommon/PEShaderSystem/Config/PEShaderConfigInput.h" + +#include "Common/Logging/Log.h" +#include "Common/VariantUtil.h" +#include "VideoCommon/RenderState.h" + +namespace VideoCommon::PE +{ +std::optional DeserializeInputFromConfig(const picojson::object& obj, + std::size_t texture_unit, + std::size_t parent_pass_index, + std::size_t total_passes) +{ + const auto type_iter = obj.find("type"); + if (type_iter == obj.end()) + { + ERROR_LOG_FMT(VIDEO, "Failed to load shader configuration file, input 'type' not found"); + return std::nullopt; + } + + const auto process_common = [&](auto& input) -> bool { + input.m_texture_unit = static_cast(texture_unit); + + input.m_sampler_state = RenderState::GetLinearSamplerState(); + + const auto sampler_state_mode_iter = obj.find("texture_mode"); + if (sampler_state_mode_iter == obj.end()) + { + ERROR_LOG_FMT(VIDEO, + "Failed to load shader configuration file, input 'texture_mode' not found"); + return false; + } + if (!sampler_state_mode_iter->second.is()) + { + ERROR_LOG_FMT( + VIDEO, + "Failed to load shader configuration file, input 'texture_mode' is not the right type"); + return false; + } + std::string sampler_state_mode = sampler_state_mode_iter->second.to_str(); + std::transform(sampler_state_mode.begin(), sampler_state_mode.end(), sampler_state_mode.begin(), + [](unsigned char c) { return std::tolower(c); }); + + // TODO: add "Border" + if (sampler_state_mode == "clamp") + { + input.m_sampler_state.wrap_u = SamplerState::AddressMode::Clamp; + input.m_sampler_state.wrap_v = SamplerState::AddressMode::Clamp; + } + else if (sampler_state_mode == "repeat") + { + input.m_sampler_state.wrap_u = SamplerState::AddressMode::Repeat; + input.m_sampler_state.wrap_v = SamplerState::AddressMode::Repeat; + } + else if (sampler_state_mode == "mirrored_repeat") + { + input.m_sampler_state.wrap_u = SamplerState::AddressMode::MirroredRepeat; + input.m_sampler_state.wrap_v = SamplerState::AddressMode::MirroredRepeat; + } + else + { + ERROR_LOG_FMT(VIDEO, + "Failed to load shader configuration file, input 'texture_mode' has an invalid " + "value '{}'", + sampler_state_mode); + return false; + } + + const auto sampler_state_filter_iter = obj.find("texture_filter"); + if (sampler_state_filter_iter == obj.end()) + { + ERROR_LOG_FMT(VIDEO, + "Failed to load shader configuration file, input 'texture_filter' not found"); + return false; + } + if (!sampler_state_filter_iter->second.is()) + { + ERROR_LOG_FMT( + VIDEO, + "Failed to load shader configuration file, input 'texture_filter' is not the right type"); + return false; + } + std::string sampler_state_filter = sampler_state_filter_iter->second.to_str(); + std::transform(sampler_state_filter.begin(), sampler_state_filter.end(), + sampler_state_filter.begin(), [](unsigned char c) { return std::tolower(c); }); + if (sampler_state_filter == "linear") + { + input.m_sampler_state.min_filter = SamplerState::Filter::Linear; + input.m_sampler_state.mag_filter = SamplerState::Filter::Linear; + input.m_sampler_state.mipmap_filter = SamplerState::Filter::Linear; + } + else if (sampler_state_filter == "point") + { + input.m_sampler_state.min_filter = SamplerState::Filter::Linear; + input.m_sampler_state.mag_filter = SamplerState::Filter::Linear; + input.m_sampler_state.mipmap_filter = SamplerState::Filter::Linear; + } + else + { + ERROR_LOG_FMT( + VIDEO, + "Failed to load shader configuration file, input 'texture_filter' has an invalid " + "value '{}'", + sampler_state_filter); + return false; + } + + return true; + }; + + std::string type = type_iter->second.to_str(); + std::transform(type.begin(), type.end(), type.begin(), + [](unsigned char c) { return std::tolower(c); }); + if (type == "user_image") + { + UserImage input; + if (process_common(input)) + return input; + } + else if (type == "internal_image") + { + InternalImage input; + if (!process_common(input)) + return std::nullopt; + + const auto path_iter = obj.find("path"); + if (path_iter == obj.end()) + { + ERROR_LOG_FMT(VIDEO, "Failed to load shader configuration file, input 'path' not found"); + return std::nullopt; + } + if (!path_iter->second.is()) + { + ERROR_LOG_FMT(VIDEO, + "Failed to load shader configuration file, input 'path' is not the right type"); + return std::nullopt; + } + input.m_path = path_iter->second.to_str(); + + return input; + } + else if (type == "color_buffer") + { + ColorBuffer input; + if (process_common(input)) + return input; + } + else if (type == "depth_buffer") + { + DepthBuffer input; + if (process_common(input)) + return input; + } + else if (type == "previous_shader") + { + PreviousShader input; + if (process_common(input)) + return input; + } + else if (type == "previous_pass") + { + PreviousPass input; + input.m_parent_pass_index = static_cast(parent_pass_index); + if (process_common(input)) + return input; + } + else if (type == "explicit_pass") + { + ExplicitPass input; + if (!process_common(input)) + return std::nullopt; + + const auto index_iter = obj.find("index"); + if (index_iter == obj.end()) + { + ERROR_LOG_FMT(VIDEO, "Failed to load shader configuration file, input 'index' not found"); + return std::nullopt; + } + if (!index_iter->second.is()) + { + ERROR_LOG_FMT( + VIDEO, "Failed to load shader configuration file, input 'index' is not the right type"); + return std::nullopt; + } + input.m_pass_index = static_cast(index_iter->second.get()); + if (input.m_pass_index >= static_cast(total_passes)) + { + ERROR_LOG_FMT(VIDEO, + "Failed to load shader configuration file, input 'index' exceeds max number of " + "passes '{}'", + total_passes); + return std::nullopt; + } + + return input; + } + else + { + ERROR_LOG_FMT(VIDEO, "Failed to load shader configuration file, input invalid type '{}'", type); + } + + return std::nullopt; +} + +void SerializeInputToProfile(picojson::object& obj, const ShaderConfigInput& input) +{ + std::visit(overloaded{[&](const UserImage& i) { obj["image_path"] = picojson::value{i.m_path}; }, + [&](const InternalImage& i) {}, [&](const ColorBuffer& i) {}, + [&](const DepthBuffer& i) {}, [&](const PreviousPass& i) {}, + [&](const PreviousShader& i) {}, [&](const ExplicitPass& i) {}}, + input); +} + +void DeserializeInputFromProfile(const picojson::object& obj, ShaderConfigInput& input) +{ + std::visit(overloaded{[&](UserImage& i) { + if (auto it = obj.find("image_path"); it != obj.end()) + { + i.m_path = it->second.to_str(); + } + }, + [&](InternalImage& i) {}, [&](ColorBuffer& i) {}, [&](DepthBuffer& i) {}, + [&](PreviousPass& i) {}, [&](PreviousShader& i) {}, + [&](ExplicitPass& i) {}}, + input); +} +} // namespace VideoCommon::PE diff --git a/Source/Core/VideoCommon/PEShaderSystem/Config/PEShaderConfigInput.h b/Source/Core/VideoCommon/PEShaderSystem/Config/PEShaderConfigInput.h new file mode 100644 index 000000000000..61cd961691f2 --- /dev/null +++ b/Source/Core/VideoCommon/PEShaderSystem/Config/PEShaderConfigInput.h @@ -0,0 +1,71 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include + +#include + +#include "Common/CommonTypes.h" +#include "VideoCommon/RenderState.h" + +namespace VideoCommon::PE +{ +struct UserImage +{ + SamplerState m_sampler_state; + u32 m_texture_unit = 0; + std::string m_path; +}; + +struct InternalImage +{ + SamplerState m_sampler_state; + u32 m_texture_unit = 0; + std::string m_path; +}; + +struct ColorBuffer +{ + SamplerState m_sampler_state; + u32 m_texture_unit = 0; +}; + +struct DepthBuffer +{ + SamplerState m_sampler_state; + u32 m_texture_unit = 0; +}; + +struct PreviousShader +{ + SamplerState m_sampler_state; + u32 m_texture_unit = 0; +}; + +struct PreviousPass +{ + SamplerState m_sampler_state; + u32 m_texture_unit = 0; + u32 m_parent_pass_index; +}; + +struct ExplicitPass +{ + SamplerState m_sampler_state; + u32 m_texture_unit = 0; + u16 m_pass_index = 0; +}; + +using ShaderConfigInput = + std::variant; + +std::optional DeserializeInputFromConfig(const picojson::object& obj, std::size_t texture_unit, std::size_t pass_index, std::size_t total_passes); +void SerializeInputToProfile(picojson::object& obj, const ShaderConfigInput& input); +void DeserializeInputFromProfile(const picojson::object& obj, ShaderConfigInput& input); + +} // namespace VideoCommon::PE diff --git a/Source/Core/VideoCommon/PEShaderSystem/Config/PEShaderConfigOption.cpp b/Source/Core/VideoCommon/PEShaderSystem/Config/PEShaderConfigOption.cpp new file mode 100644 index 000000000000..c533548feeb9 --- /dev/null +++ b/Source/Core/VideoCommon/PEShaderSystem/Config/PEShaderConfigOption.cpp @@ -0,0 +1,743 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "VideoCommon/PEShaderSystem/Config/PEShaderConfigOption.h" + +#include + +#include "Common/Logging/Log.h" +#include "Common/VariantUtil.h" + +namespace VideoCommon::PE +{ +namespace +{ +template +bool ProcessNumeric(OptionType& option, const picojson::object& obj) +{ + using option_value_t = typename OptionType::ValueType; + using element_type_t = + std::remove_reference_t()))>; + const auto min_iter = obj.find("min"); + if (min_iter != obj.end() && min_iter->second.is()) + { + const auto min_json = min_iter->second.get(); + if (!std::all_of(min_json.begin(), min_json.end(), + [](const picojson::value& v) { return v.is(); })) + { + ERROR_LOG_FMT(VIDEO, "Failed to load shader configuration file, enum option 'min' " + "should contain only doubles"); + return false; + } + + if (min_json.size() != option.m_min_value.size()) + { + ERROR_LOG_FMT(VIDEO, + "Failed to load shader configuration file, enum option 'min' " + "has unexpected number of elements, expected {}", + option.m_min_value.size()); + return false; + } + for (std::size_t i = 0; i < min_json.size(); i++) + { + option.m_min_value[i] = static_cast(min_json[i].get()); + } + } + + const auto max_iter = obj.find("max"); + if (max_iter != obj.end() && max_iter->second.is()) + { + const auto max_json = max_iter->second.get(); + if (!std::all_of(max_json.begin(), max_json.end(), + [](const picojson::value& v) { return v.is(); })) + { + ERROR_LOG_FMT(VIDEO, "Failed to load shader configuration file, enum option 'max' " + "should contain only doubles"); + return false; + } + + if (max_json.size() != option.m_max_value.size()) + { + ERROR_LOG_FMT(VIDEO, + "Failed to load shader configuration file, enum option 'max' " + "has unexpected number of elements, expected {}", + option.m_max_value.size()); + return false; + } + for (std::size_t i = 0; i < max_json.size(); i++) + { + option.m_max_value[i] = static_cast(max_json[i].get()); + } + } + + const auto default_iter = obj.find("default"); + if (default_iter != obj.end() && default_iter->second.is()) + { + const auto default_json = default_iter->second.get(); + if (!std::all_of(default_json.begin(), default_json.end(), + [](const picojson::value& v) { return v.is(); })) + { + ERROR_LOG_FMT(VIDEO, "Failed to load shader configuration file, enum option 'default' " + "should contain only doubles"); + return false; + } + + if (default_json.size() != option.m_default_value.size()) + { + ERROR_LOG_FMT(VIDEO, + "Failed to load shader configuration file, enum option 'default' " + "has unexpected number of elements, expected {}", + option.m_default_value.size()); + return false; + } + for (std::size_t i = 0; i < default_json.size(); i++) + { + option.m_default_value[i] = static_cast(default_json[i].get()); + option.m_value[i] = option.m_default_value[i]; + } + } + + const auto step_iter = obj.find("step"); + if (step_iter == obj.end() || !step_iter->second.is()) + { + for (std::size_t i = 0; i < option.m_step_size.size(); i++) + { + option.m_step_size[i] = 1; + } + } + else + { + const auto step_json = step_iter->second.get(); + if (!std::all_of(step_json.begin(), step_json.end(), + [](const picojson::value& v) { return v.is(); })) + { + ERROR_LOG_FMT(VIDEO, "Failed to load shader configuration file, enum option 'step' " + "should contain only doubles"); + return false; + } + + if (step_json.size() != option.m_step_size.size()) + { + ERROR_LOG_FMT(VIDEO, + "Failed to load shader configuration file, enum option 'default' " + "has unexpected number of elements, expected {}", + option.m_step_size.size()); + return false; + } + for (std::size_t i = 0; i < step_json.size(); i++) + { + option.m_step_size[i] = static_cast(step_json[i].get()); + } + } + return true; +} +} // namespace +bool IsValid(const ShaderConfigOption& option) +{ + return false; +} + +void Reset(ShaderConfigOption& option) +{ + std::visit(overloaded{[&](EnumChoiceOption& o) { o.m_index = o.m_default_index; }, + [&](FloatOption<1>& o) { o.m_value = o.m_default_value; }, + [&](FloatOption<2>& o) { o.m_value = o.m_default_value; }, + [&](FloatOption<3>& o) { o.m_value = o.m_default_value; }, + [&](FloatOption<4>& o) { o.m_value = o.m_default_value; }, + [&](IntOption<1>& o) { o.m_value = o.m_default_value; }, + [&](IntOption<2>& o) { o.m_value = o.m_default_value; }, + [&](IntOption<3>& o) { o.m_value = o.m_default_value; }, + [&](IntOption<4>& o) { o.m_value = o.m_default_value; }, + [&](BoolOption& o) { o.m_value = o.m_default_value; }, + [&](ColorOption& o) { o.m_value = o.m_default_value; }, + [&](ColorAlphaOption& o) { o.m_value = o.m_default_value; }}, + option); +} + +void LoadSnapshot(ShaderConfigOption& option, std::size_t channel) +{ + const auto load_snapshot = [&](auto& o) { + if (channel < o.m_snapshots.m_values.size() && o.m_snapshots.m_values[channel]) + { + o.m_value = *o.m_snapshots.m_values[channel]; + } + }; + std::visit( + overloaded{ + [&](EnumChoiceOption& o) { + if (channel < o.m_snapshots.m_values.size() && o.m_snapshots.m_values[channel]) + { + o.m_index = *o.m_snapshots.m_values[channel]; + } + }, + [&](FloatOption<1>& o) { load_snapshot(o); }, + [&](FloatOption<2>& o) { load_snapshot(o); }, + [&](FloatOption<3>& o) { load_snapshot(o); }, + [&](FloatOption<4>& o) { load_snapshot(o); }, [&](IntOption<1>& o) { load_snapshot(o); }, + [&](IntOption<2>& o) { load_snapshot(o); }, [&](IntOption<3>& o) { load_snapshot(o); }, + [&](IntOption<4>& o) { load_snapshot(o); }, [&](BoolOption& o) { load_snapshot(o); }, + [&](ColorOption& o) { load_snapshot(o); }, + [&](ColorAlphaOption& o) { load_snapshot(o); }}, + option); +} + +void SaveSnapshot(ShaderConfigOption& option, std::size_t channel) +{ + const auto save_snapshot = [&](auto& o) { + if (channel < o.m_snapshots.m_values.size()) + { + o.m_snapshots.m_values[channel] = o.m_value; + } + }; + std::visit( + overloaded{ + [&](EnumChoiceOption& o) { + if (channel < o.m_snapshots.m_values.size()) + { + o.m_snapshots.m_values[channel] = o.m_index; + } + }, + [&](FloatOption<1>& o) { save_snapshot(o); }, + [&](FloatOption<2>& o) { save_snapshot(o); }, + [&](FloatOption<3>& o) { save_snapshot(o); }, + [&](FloatOption<4>& o) { save_snapshot(o); }, [&](IntOption<1>& o) { save_snapshot(o); }, + [&](IntOption<2>& o) { save_snapshot(o); }, [&](IntOption<3>& o) { save_snapshot(o); }, + [&](IntOption<4>& o) { save_snapshot(o); }, [&](BoolOption& o) { save_snapshot(o); }, + [&](ColorOption& o) { save_snapshot(o); }, + [&](ColorAlphaOption& o) { save_snapshot(o); }}, + option); +} + +bool HasSnapshot(const ShaderConfigOption& option, std::size_t channel) +{ + bool result = false; + const auto set_has_snapshot = [&](auto& o) { + result = channel < o.m_snapshots.m_values.size() && o.m_snapshots.m_values[channel].has_value(); + }; + std::visit(overloaded{[&](const EnumChoiceOption& o) { set_has_snapshot(o); }, + [&](const FloatOption<1>& o) { set_has_snapshot(o); }, + [&](const FloatOption<2>& o) { set_has_snapshot(o); }, + [&](const FloatOption<3>& o) { set_has_snapshot(o); }, + [&](const FloatOption<4>& o) { set_has_snapshot(o); }, + [&](const IntOption<1>& o) { set_has_snapshot(o); }, + [&](const IntOption<2>& o) { set_has_snapshot(o); }, + [&](const IntOption<3>& o) { set_has_snapshot(o); }, + [&](const IntOption<4>& o) { set_has_snapshot(o); }, + [&](const BoolOption& o) { set_has_snapshot(o); }, + [&](const ColorOption& o) { set_has_snapshot(o); }, + [&](const ColorAlphaOption& o) { set_has_snapshot(o); }}, + option); + return result; +} + +std::string GetOptionGroup(const ShaderConfigOption& option) +{ + std::string result; + + const auto set_group = [&](auto& o) { result = o.m_common.m_group_name; }; + std::visit(overloaded{[&](const EnumChoiceOption& o) { set_group(o); }, + [&](const FloatOption<1>& o) { set_group(o); }, + [&](const FloatOption<2>& o) { set_group(o); }, + [&](const FloatOption<3>& o) { set_group(o); }, + [&](const FloatOption<4>& o) { set_group(o); }, + [&](const IntOption<1>& o) { set_group(o); }, + [&](const IntOption<2>& o) { set_group(o); }, + [&](const IntOption<3>& o) { set_group(o); }, + [&](const IntOption<4>& o) { set_group(o); }, + [&](const BoolOption& o) { set_group(o); }, + [&](const ColorOption& o) { set_group(o); }, + [&](const ColorAlphaOption& o) { set_group(o); }}, + option); + + return result; +} + +bool IsUnsatisfiedDependency(const ShaderConfigOption& option, const std::string& dependency_name) +{ + bool result = false; + std::visit(overloaded{[&](const EnumChoiceOption& o) {}, [&](const FloatOption<1>& o) {}, + [&](const FloatOption<2>& o) {}, [&](const FloatOption<3>& o) {}, + [&](const FloatOption<4>& o) {}, [&](const IntOption<1>& o) {}, + [&](const IntOption<2>& o) {}, [&](const IntOption<3>& o) {}, + [&](const IntOption<4>& o) {}, + [&](const BoolOption& o) { + result = o.m_common.m_name == dependency_name && !o.m_value; + }, + [&](const ColorOption& o) {}, [&](const ColorAlphaOption& o) {}}, + option); + + return result; +} + +std::optional DeserializeOptionFromConfig(const picojson::object& obj) +{ + const auto type_iter = obj.find("type"); + if (type_iter == obj.end()) + { + ERROR_LOG_FMT(VIDEO, "Failed to load shader configuration file, option 'type' not found"); + return std::nullopt; + } + + const auto process_common = [&](auto& option) -> bool { + const auto name_iter = obj.find("name"); + if (name_iter == obj.end()) + { + ERROR_LOG_FMT(VIDEO, "Failed to load shader configuration file, option 'name' not found"); + return false; + } + if (!name_iter->second.is()) + { + ERROR_LOG_FMT(VIDEO, + "Failed to load shader configuration file, option 'name' is not a string type"); + return false; + } + option.m_common.m_name = name_iter->second.to_str(); + + const auto ui_name_iter = obj.find("ui_name"); + if (ui_name_iter == obj.end() || !ui_name_iter->second.is()) + { + option.m_common.m_ui_name = option.m_common.m_name; + } + else + { + option.m_common.m_ui_name = ui_name_iter->second.to_str(); + } + + const auto ui_description_iter = obj.find("ui_description"); + if (ui_description_iter != obj.end() && ui_description_iter->second.is()) + { + option.m_common.m_ui_description = ui_description_iter->second.to_str(); + } + + const auto dependent_option_iter = obj.find("dependent_option"); + if (dependent_option_iter != obj.end() && dependent_option_iter->second.is()) + { + option.m_common.m_dependent_option = dependent_option_iter->second.to_str(); + } + + const auto group_name_iter = obj.find("group_name"); + if (group_name_iter != obj.end() && group_name_iter->second.is()) + { + option.m_common.m_group_name = group_name_iter->second.to_str(); + } + + const auto is_constant_iter = obj.find("is_constant"); + if (is_constant_iter != obj.end() && is_constant_iter->second.is()) + { + option.m_common.m_compile_time_constant = is_constant_iter->second.get(); + } + + return true; + }; + + const auto process_float_precision = [&](auto& option) { + const auto precision_iter = obj.find("precision"); + if (precision_iter != obj.end() && precision_iter->second.is()) + { + const auto precision_json = precision_iter->second.get(); + if (!std::all_of(precision_json.begin(), precision_json.end(), + [](const picojson::value& v) { return v.is(); })) + { + ERROR_LOG_FMT(VIDEO, "Failed to load shader configuration file, float option 'precision' " + "should contain only doubles"); + return false; + } + + if (precision_json.size() != option.m_decimal_precision.size()) + { + ERROR_LOG_FMT(VIDEO, + "Failed to load shader configuration file, float option 'precision' " + "has unexpected number of elements, expected {}", + option.m_decimal_precision.size()); + return false; + } + for (std::size_t i = 0; i < precision_json.size(); i++) + { + option.m_decimal_precision[i] = static_cast(precision_json[i].get()); + } + } + return true; + }; + + std::string type = type_iter->second.to_str(); + std::transform(type.begin(), type.end(), type.begin(), + [](unsigned char c) { return std::tolower(c); }); + if (type == "bool") + { + BoolOption option; + if (!process_common(option)) + return std::nullopt; + const auto default_iter = obj.find("default"); + if (default_iter != obj.end() && default_iter->second.is()) + { + option.m_default_value = default_iter->second.get(); + option.m_value = option.m_default_value; + } + return option; + } + else if (type == "enum") + { + EnumChoiceOption option; + if (!process_common(option)) + return std::nullopt; + const auto ui_choices_iter = obj.find("ui_choices"); + if (ui_choices_iter == obj.end() || !ui_choices_iter->second.is()) + { + ERROR_LOG_FMT( + VIDEO, "Failed to load shader configuration file, enum option 'ui_choices' is not valid"); + return std::nullopt; + } + + const auto ui_choices_json = ui_choices_iter->second.get(); + if (!std::all_of(ui_choices_json.begin(), ui_choices_json.end(), + [](const picojson::value& v) { return v.is(); })) + { + ERROR_LOG_FMT(VIDEO, "Failed to load shader configuration file, enum option 'ui_choices' " + "should contain only strings"); + return std::nullopt; + } + + const auto values_iter = obj.find("values"); + if (values_iter == obj.end() || !values_iter->second.is()) + { + ERROR_LOG_FMT(VIDEO, + "Failed to load shader configuration file, enum option 'values' is not valid"); + return std::nullopt; + } + + const auto values_json = values_iter->second.get(); + if (!std::all_of(values_json.begin(), values_json.end(), + [](const picojson::value& v) { return v.is(); })) + { + ERROR_LOG_FMT(VIDEO, "Failed to load shader configuration file, enum option 'values' should " + "contain only doubles"); + return std::nullopt; + } + + if (ui_choices_json.size() != values_json.size()) + { + ERROR_LOG_FMT(VIDEO, "Failed to load shader configuration file, enum option 'ui_choices' and " + "'values' does not match!"); + return std::nullopt; + } + + for (const auto& ui_choice_json : ui_choices_json) + { + option.m_ui_choices.push_back(ui_choice_json.to_str()); + } + + for (const auto& value_json : values_json) + { + option.m_values_for_choices.push_back(static_cast(value_json.get())); + } + + const auto default_index_iter = obj.find("default_index"); + if (default_index_iter != obj.end() && default_index_iter->second.is()) + { + option.m_default_index = static_cast(default_index_iter->second.get()); + option.m_index = option.m_default_index; + } + + return option; + } + else if (type == "float") + { + FloatOption<1> option; + if (!process_common(option)) + return std::nullopt; + if (!ProcessNumeric(option, obj)) + return std::nullopt; + if (!process_float_precision(option)) + return std::nullopt; + return option; + } + else if (type == "float2") + { + FloatOption<2> option; + if (!process_common(option)) + return std::nullopt; + if (!ProcessNumeric(option, obj)) + return std::nullopt; + if (!process_float_precision(option)) + return std::nullopt; + return option; + } + else if (type == "float3") + { + FloatOption<3> option; + if (!process_common(option)) + return std::nullopt; + if (!ProcessNumeric(option, obj)) + return std::nullopt; + if (!process_float_precision(option)) + return std::nullopt; + return option; + } + else if (type == "float4") + { + FloatOption<4> option; + if (!process_common(option)) + return std::nullopt; + if (!ProcessNumeric(option, obj)) + return std::nullopt; + if (!process_float_precision(option)) + return std::nullopt; + return option; + } + else if (type == "int") + { + IntOption<1> option; + if (!process_common(option)) + return std::nullopt; + if (!ProcessNumeric(option, obj)) + return std::nullopt; + return option; + } + else if (type == "int2") + { + IntOption<2> option; + if (!process_common(option)) + return std::nullopt; + if (!ProcessNumeric(option, obj)) + return std::nullopt; + return option; + } + else if (type == "int3") + { + IntOption<3> option; + if (!process_common(option)) + return std::nullopt; + if (!ProcessNumeric(option, obj)) + return std::nullopt; + return option; + } + else if (type == "int4") + { + IntOption<4> option; + if (!process_common(option)) + return std::nullopt; + if (!ProcessNumeric(option, obj)) + return std::nullopt; + return option; + } + else if (type == "rgb") + { + ColorOption option; + if (!process_common(option)) + return std::nullopt; + if (!ProcessNumeric(option, obj)) + return std::nullopt; + return option; + } + else if (type == "rgba") + { + ColorAlphaOption option; + if (!process_common(option)) + return std::nullopt; + if (!ProcessNumeric(option, obj)) + return std::nullopt; + return option; + } + else + { + ERROR_LOG_FMT(VIDEO, "Failed to load shader configuration file, option invalid type '{}'", + type); + } + + return std::nullopt; +} + +void SerializeOptionToProfile(picojson::object& obj, const ShaderConfigOption& option) +{ + const auto to_serialized_array = [](const auto& val) -> picojson::array { + picojson::array result; + for (const auto& v : val) + { + result.push_back(picojson::value{static_cast(v)}); + } + return result; + }; + std::visit(overloaded{[&](const EnumChoiceOption& o) { + obj["index"] = picojson::value{static_cast(o.m_index)}; + obj["name"] = picojson::value{o.m_common.m_name}; + }, + [&](const FloatOption<1>& o) { + obj["values"] = picojson::value{to_serialized_array(o.m_value)}; + obj["name"] = picojson::value{o.m_common.m_name}; + }, + [&](const FloatOption<2>& o) { + obj["values"] = picojson::value{to_serialized_array(o.m_value)}; + obj["name"] = picojson::value{o.m_common.m_name}; + }, + [&](const FloatOption<3>& o) { + obj["values"] = picojson::value{to_serialized_array(o.m_value)}; + obj["name"] = picojson::value{o.m_common.m_name}; + }, + [&](const FloatOption<4>& o) { + obj["values"] = picojson::value{to_serialized_array(o.m_value)}; + obj["name"] = picojson::value{o.m_common.m_name}; + }, + [&](const IntOption<1>& o) { + obj["values"] = picojson::value{to_serialized_array(o.m_value)}; + obj["name"] = picojson::value{o.m_common.m_name}; + }, + [&](const IntOption<2>& o) { + obj["values"] = picojson::value{to_serialized_array(o.m_value)}; + obj["name"] = picojson::value{o.m_common.m_name}; + }, + [&](const IntOption<3>& o) { + obj["values"] = picojson::value{to_serialized_array(o.m_value)}; + obj["name"] = picojson::value{o.m_common.m_name}; + }, + [&](const IntOption<4>& o) { + obj["values"] = picojson::value{to_serialized_array(o.m_value)}; + obj["name"] = picojson::value{o.m_common.m_name}; + }, + [&](const BoolOption& o) { + obj["value"] = picojson::value{o.m_value}; + obj["name"] = picojson::value{o.m_common.m_name}; + }, + [&](const ColorOption& o) { + obj["values"] = picojson::value{to_serialized_array(o.m_value)}; + obj["name"] = picojson::value{o.m_common.m_name}; + }, + [&](const ColorAlphaOption& o) { + obj["values"] = picojson::value{to_serialized_array(o.m_value)}; + obj["name"] = picojson::value{o.m_common.m_name}; + }}, + option); +} + +void DeserializeOptionFromProfile(const picojson::object& obj, ShaderConfigOption& option) +{ + const auto set_values = [&](auto& values) { + if (auto it = obj.find("values"); it != obj.end()) + { + if (it->second.is()) + { + auto values_serialized = it->second.get(); + if (values_serialized.size() == values.size()) + { + for (std::size_t i = 0; i < values_serialized.size(); i++) + { + if (values_serialized[i].is()) + { + values[i] = static_cast::type>( + values_serialized[i].get()); + } + } + } + } + } + }; + const auto name_matches = [&](const auto& o) -> bool { + if (const auto it = obj.find("name"); it != obj.end()) + { + if (it->second.is()) + { + return it->second.to_str() == o.m_common.m_name; + } + } + return false; + }; + + std::visit(overloaded{[&](EnumChoiceOption& o) { + if (!name_matches(o)) + { + return; + } + if (auto it = obj.find("index"); it != obj.end()) + { + if (it->second.is()) + { + o.m_index = static_cast(it->second.get()); + } + } + }, + [&](FloatOption<1>& o) { + if (!name_matches(o)) + { + return; + } + set_values(o.m_value); + }, + [&](FloatOption<2>& o) { + if (!name_matches(o)) + { + return; + } + set_values(o.m_value); + }, + [&](FloatOption<3>& o) { + if (!name_matches(o)) + { + return; + } + set_values(o.m_value); + }, + [&](FloatOption<4>& o) { + if (!name_matches(o)) + { + return; + } + set_values(o.m_value); + }, + [&](IntOption<1>& o) { + if (!name_matches(o)) + { + return; + } + set_values(o.m_value); + }, + [&](IntOption<2>& o) { + if (!name_matches(o)) + { + return; + } + set_values(o.m_value); + }, + [&](IntOption<3>& o) { + if (!name_matches(o)) + { + return; + } + set_values(o.m_value); + }, + [&](IntOption<4>& o) { + if (!name_matches(o)) + { + return; + } + set_values(o.m_value); + }, + [&](BoolOption& o) { + if (!name_matches(o)) + { + return; + } + if (auto it = obj.find("value"); it != obj.end()) + { + if (it->second.is()) + { + o.m_value = it->second.get(); + } + } + }, + [&](ColorOption& o) { + if (!name_matches(o)) + { + return; + } + set_values(o.m_value); + }, + [&](ColorAlphaOption& o) { + if (!name_matches(o)) + { + return; + } + set_values(o.m_value); + }}, + option); +} +} // namespace VideoCommon::PE diff --git a/Source/Core/VideoCommon/PEShaderSystem/Config/PEShaderConfigOption.h b/Source/Core/VideoCommon/PEShaderSystem/Config/PEShaderConfigOption.h new file mode 100644 index 000000000000..9d2c256a477c --- /dev/null +++ b/Source/Core/VideoCommon/PEShaderSystem/Config/PEShaderConfigOption.h @@ -0,0 +1,122 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include + +#include + +#include "Common/CommonTypes.h" + +namespace VideoCommon::PE +{ +struct CommonOptionContext +{ + std::string m_ui_name; + std::string m_ui_description; + std::string m_name; + std::string m_dependent_option; + std::string m_group_name; + + bool m_compile_time_constant = false; +}; + +const constexpr std::size_t MAX_SNAPSHOTS = 5; + +template +struct Snapshots +{ + std::array m_values; +}; + +struct EnumChoiceOption +{ + using ValueType = s32; + CommonOptionContext m_common; + + std::vector m_ui_choices = {}; + std::vector m_values_for_choices = {}; + u32 m_index = 0; + u32 m_default_index = 0; + + Snapshots> m_snapshots; +}; + +struct BoolOption +{ + CommonOptionContext m_common; + + bool m_default_value = false; + bool m_value = false; + + Snapshots> m_snapshots; +}; + +template +struct FloatOption +{ + using ValueType = std::array; + CommonOptionContext m_common; + + ValueType m_default_value = {}; + ValueType m_min_value = {}; + ValueType m_max_value = {}; + ValueType m_value = {}; + ValueType m_step_size = {}; + ValueType m_decimal_precision = {}; + + Snapshots> m_snapshots; +}; + +template +struct IntOption +{ + using ValueType = std::array; + CommonOptionContext m_common; + + ValueType m_default_value = {}; + ValueType m_min_value = {}; + ValueType m_max_value = {}; + ValueType m_value = {}; + ValueType m_step_size = {}; + + Snapshots> m_snapshots; +}; + +struct ColorOption final : public FloatOption<3> +{ + using FloatOption<3>::FloatOption; + using ValueType = FloatOption<3>::ValueType; +}; + +struct ColorAlphaOption final : public FloatOption<4> +{ + using FloatOption<4>::FloatOption; + using ValueType = FloatOption<4>::ValueType; +}; + +// TODO: game-memory option... + +using ShaderConfigOption = + std::variant, FloatOption<2>, FloatOption<3>, FloatOption<4>, + IntOption<1>, IntOption<2>, IntOption<3>, IntOption<4>, BoolOption, ColorOption, + ColorAlphaOption>; + +bool IsValid(const ShaderConfigOption& option); +void Reset(ShaderConfigOption& option); +void LoadSnapshot(ShaderConfigOption& option, std::size_t channel); +void SaveSnapshot(ShaderConfigOption& option, std::size_t channel); +bool HasSnapshot(const ShaderConfigOption& option, std::size_t channel); +std::string GetOptionGroup(const ShaderConfigOption& option); +bool IsUnsatisfiedDependency(const ShaderConfigOption& option, const std::string& dependency_name); +std::optional DeserializeOptionFromConfig(const picojson::object& obj); + +void SerializeOptionToProfile(picojson::object& obj, const ShaderConfigOption& option); +void DeserializeOptionFromProfile(const picojson::object& obj, ShaderConfigOption& option); +} // namespace VideoCommon::PE diff --git a/Source/Core/VideoCommon/PEShaderSystem/Config/PEShaderConfigPass.cpp b/Source/Core/VideoCommon/PEShaderSystem/Config/PEShaderConfigPass.cpp new file mode 100644 index 000000000000..3e9b43b4b577 --- /dev/null +++ b/Source/Core/VideoCommon/PEShaderSystem/Config/PEShaderConfigPass.cpp @@ -0,0 +1,111 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "VideoCommon/PEShaderSystem/Config/PEShaderConfigPass.h" + +#include "Common/Logging/Log.h" + +namespace VideoCommon::PE +{ +bool ShaderConfigPass::DeserializeFromConfig(const picojson::object& obj, std::size_t pass_index, + std::size_t total_passes) +{ + const auto entry_point_iter = obj.find("entry_point"); + if (entry_point_iter == obj.end()) + { + ERROR_LOG_FMT(VIDEO, + "Failed to load shader configuration file, 'entry_point' in pass not found"); + return false; + } + m_entry_point = entry_point_iter->second.to_str(); + + const auto dependent_option_iter = obj.find("dependent_option"); + if (dependent_option_iter != obj.end() && dependent_option_iter->second.is()) + { + m_dependent_option = dependent_option_iter->second.to_str(); + } + + const auto inputs_iter = obj.find("inputs"); + if (inputs_iter == obj.end()) + { + ERROR_LOG_FMT(VIDEO, "Failed to load shader configuration file, 'inputs' in pass not found"); + return false; + } + if (inputs_iter->second.is()) + { + const auto& inputs_arr = inputs_iter->second.get(); + for (std::size_t i = 0; i < inputs_arr.size(); i++) + { + const auto& input_val = inputs_arr[i]; + if (!input_val.is()) + { + ERROR_LOG_FMT( + VIDEO, + "Failed to load shader configuration file, specified input is not a json object"); + } + + if (const auto input = DeserializeInputFromConfig(input_val.get(), i, + pass_index, total_passes)) + { + m_inputs.push_back(*input); + } + else + { + return false; + } + } + } + + return true; +} + +void ShaderConfigPass::SerializeToProfile(picojson::object& obj) const +{ + picojson::array serialized_inputs; + for (const auto& input : m_inputs) + { + picojson::object serialized_input; + SerializeInputToProfile(serialized_input, input); + serialized_inputs.push_back(picojson::value{serialized_input}); + } + obj["inputs"] = picojson::value{serialized_inputs}; +} + +void ShaderConfigPass::DeserializeFromProfile(const picojson::object& obj) +{ + if (auto it = obj.find("inputs"); it != obj.end()) + { + if (it->second.is()) + { + auto serialized_inputs = it->second.get(); + if (serialized_inputs.size() != m_inputs.size()) + return; + + for (std::size_t i = 0; i < serialized_inputs.size(); i++) + { + const auto& serialized_input_val = serialized_inputs[i]; + if (serialized_input_val.is()) + { + const auto& serialized_input = serialized_input_val.get(); + DeserializeInputFromProfile(serialized_input, m_inputs[i]); + } + } + } + } +} + +ShaderConfigPass ShaderConfigPass::CreateDefaultPass() +{ + PreviousPass previous_pass; + previous_pass.m_parent_pass_index = 0; + previous_pass.m_sampler_state = RenderState::GetLinearSamplerState(); + + ShaderConfigPass result; + result.m_inputs.push_back(previous_pass); + result.m_entry_point = "main"; + result.m_output_scale = 1.0f; + + return result; +} +} // namespace VideoCommon::PE diff --git a/Source/Core/VideoCommon/PEShaderSystem/Config/PEShaderConfigPass.h b/Source/Core/VideoCommon/PEShaderSystem/Config/PEShaderConfigPass.h new file mode 100644 index 000000000000..5366236dee8d --- /dev/null +++ b/Source/Core/VideoCommon/PEShaderSystem/Config/PEShaderConfigPass.h @@ -0,0 +1,30 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +#include + +#include "VideoCommon/PEShaderSystem/Config/PEShaderConfigInput.h" + +namespace VideoCommon::PE +{ +struct ShaderConfigPass +{ + std::vector m_inputs; + std::string m_entry_point; + std::string m_dependent_option; + + float m_output_scale = 1.0f; + + bool DeserializeFromConfig(const picojson::object& obj, std::size_t pass_index, std::size_t total_passes); + void SerializeToProfile(picojson::object& obj) const; + void DeserializeFromProfile(const picojson::object& obj); + + static ShaderConfigPass CreateDefaultPass(); +}; +} // namespace VideoCommon::PE diff --git a/Source/Core/VideoCommon/PEShaderSystem/Config/PETriggerConfig.cpp b/Source/Core/VideoCommon/PEShaderSystem/Config/PETriggerConfig.cpp new file mode 100644 index 000000000000..8151e74badc8 --- /dev/null +++ b/Source/Core/VideoCommon/PEShaderSystem/Config/PETriggerConfig.cpp @@ -0,0 +1,102 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "VideoCommon/PEShaderSystem/Config/PETriggerConfig.h" + +#include + +#include "Common/FileUtil.h" +#include "Common/Logging/Log.h" +#include "Common/StringUtil.h" + +namespace VideoCommon::PE +{ +namespace +{ +std::string GetStreamAsString(std::ifstream& stream) +{ + std::stringstream ss; + ss << stream.rdbuf(); + return ss.str(); +} +} // namespace +void TriggerConfig::LoadFromProfile(const std::string& file_path) +{ + std::ifstream json_stream; + File::OpenFStream(json_stream, file_path, std::ios_base::in); + if (!json_stream.is_open()) + { + ERROR_LOG_FMT(VIDEO, "Failed to load trigger json file '{}'", file_path); + return; + } + + picojson::value root; + const auto error = picojson::parse(root, GetStreamAsString(json_stream)); + + if (!error.empty()) + { + ERROR_LOG_FMT(VIDEO, "Failed to load trigger json file '{}' due to parse error: {}", file_path, + error); + return; + } + + std::map result; + if (root.is()) + { + const auto serialized_triggers = root.get(); + + for (std::size_t i = 0; i < serialized_triggers.size(); i++) + { + const auto& serialized_trigger_val = serialized_triggers[i]; + if (serialized_trigger_val.is()) + { + const auto& serialized_trigger = serialized_trigger_val.get(); + + if (const auto trigger_name_it = serialized_trigger.find("name"); trigger_name_it != serialized_trigger.end()) + { + if (const auto shaders_it = serialized_trigger.find("shaders"); + shaders_it != serialized_trigger.end()) + { + if (shaders_it->second.is()) + { + result[trigger_name_it->second.to_str()].DeserializeFromProfile( + shaders_it->second.get()); + } + } + } + } + } + } + + if (!result.empty()) + { + m_trigger_name_to_shader_groups = std::move(result); + m_changes = 0; + } +} + +void TriggerConfig::SaveToProfile(const std::string& file_path) const +{ + 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 trigger json file '{}' for writing", file_path); + return; + } + + picojson::array serialized_triggers; + for (const auto& [trigger_name, shader_group] : m_trigger_name_to_shader_groups) + { + picojson::object serialized_trigger; + serialized_trigger["name"] = picojson::value{trigger_name}; + shader_group.SerializeToProfile(serialized_trigger["shaders"]); + + serialized_triggers.push_back(picojson::value{serialized_trigger}); + } + + const auto output = picojson::value{serialized_triggers}.serialize(true); + json_stream << output; +} +} // namespace VideoCommon::PE diff --git a/Source/Core/VideoCommon/PEShaderSystem/Config/PETriggerConfig.h b/Source/Core/VideoCommon/PEShaderSystem/Config/PETriggerConfig.h new file mode 100644 index 000000000000..9d86a597e6eb --- /dev/null +++ b/Source/Core/VideoCommon/PEShaderSystem/Config/PETriggerConfig.h @@ -0,0 +1,27 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +#include + +#include "Common/CommonTypes.h" +#include "VideoCommon/PEShaderSystem/Config/PEShaderConfigGroup.h" +#include "VideoCommon/PEShaderSystem/Constants.h" + +namespace VideoCommon::PE +{ +struct TriggerConfig +{ + std::map m_trigger_name_to_shader_groups; + std::string m_chosen_trigger_point = Constants::default_trigger; + u64 m_changes = 0; + + void LoadFromProfile(const std::string& file_path); + void SaveToProfile(const std::string& file_path) const; +}; +} // namespace VideoCommon::PE diff --git a/Source/Core/VideoCommon/PEShaderSystem/Constants.h b/Source/Core/VideoCommon/PEShaderSystem/Constants.h new file mode 100644 index 000000000000..06db918b853e --- /dev/null +++ b/Source/Core/VideoCommon/PEShaderSystem/Constants.h @@ -0,0 +1,12 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include + +namespace VideoCommon::PE::Constants +{ +static const inline std::string dolphin_shipped_internal_shader_directory = "Shaders/Internal"; +static const inline std::string dolphin_shipped_public_shader_directory = "Shaders/Public"; +static const inline std::string default_trigger = "Post"; +} diff --git a/Source/Core/VideoCommon/PEShaderSystem/Runtime/BuiltinUniforms.cpp b/Source/Core/VideoCommon/PEShaderSystem/Runtime/BuiltinUniforms.cpp new file mode 100644 index 000000000000..72302eb2729b --- /dev/null +++ b/Source/Core/VideoCommon/PEShaderSystem/Runtime/BuiltinUniforms.cpp @@ -0,0 +1,93 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "VideoCommon/PEShaderSystem/Runtime/BuiltinUniforms.h" + +#include "VideoCommon/AbstractTexture.h" +#include "VideoCommon/RenderBase.h" + +namespace VideoCommon::PE +{ +BuiltinUniforms::BuiltinUniforms() + : m_prev_resolution("prev_resolution"), m_prev_rect("prev_rect"), + m_src_resolution("src_resolution"), m_window_resolution("window_resolution"), + m_src_rect("src_rect"), m_time("u_time"), m_layer("u_layer"), m_z_depth_near("z_depth_near"), + m_z_depth_far("z_depth_far"), m_output_scale("output_scale") +{ +} +std::size_t BuiltinUniforms::Size() const +{ + std::size_t result = 0; + for (const auto* option : GetOptions()) + { + result += option->Size(); + } + return result; +} +void BuiltinUniforms::WriteToMemory(u8*& buffer) const +{ + for (const auto* option : GetOptions()) + { + option->WriteToMemory(buffer); + } +} +void BuiltinUniforms::WriteShaderUniforms(ShaderCode& shader_source) const +{ + for (const auto* option : GetOptions()) + { + option->WriteShaderUniforms(shader_source); + } +} + +void BuiltinUniforms::Update(const ShaderApplyOptions& options, + const AbstractTexture* last_pass_texture, float last_pass_output_scale) +{ + const float prev_width_f = static_cast(last_pass_texture->GetWidth()); + const float prev_height_f = static_cast(last_pass_texture->GetHeight()); + const float rcp_prev_width = 1.0f / prev_width_f; + const float rcp_prev_height = 1.0f / prev_height_f; + m_prev_resolution.Update( + std::array{prev_width_f, prev_height_f, rcp_prev_width, rcp_prev_height}); + + m_prev_rect.Update(std::array{ + static_cast(options.m_source_rect.left) * rcp_prev_width, + static_cast(options.m_source_rect.top) * rcp_prev_height, + static_cast(options.m_source_rect.GetWidth()) * rcp_prev_width, + static_cast(options.m_source_rect.GetHeight()) * rcp_prev_height}); + + const float src_width_f = static_cast(options.m_source_color_tex->GetWidth()); + const float src_height_f = static_cast(options.m_source_color_tex->GetHeight()); + const float rcp_src_width = 1.0f / src_width_f; + const float rcp_src_height = 1.0f / src_height_f; + m_src_resolution.Update( + std::array{src_width_f, src_height_f, rcp_src_width, rcp_src_height}); + + const auto& window_rect = g_renderer->GetTargetRectangle(); + const float window_width_f = static_cast(window_rect.GetWidth()); + const float window_height_f = static_cast(window_rect.GetHeight()); + const float rcp_window_width = 1.0f / window_width_f; + const float rcp_window_height = 1.0f / window_height_f; + m_window_resolution.Update( + std::array{window_width_f, window_height_f, rcp_window_width, rcp_window_height}); + + m_src_rect.Update( + std::array{static_cast(options.m_source_rect.left) * rcp_src_width, + static_cast(options.m_source_rect.top) * rcp_src_height, + static_cast(options.m_source_rect.GetWidth()) * rcp_src_width, + static_cast(options.m_source_rect.GetHeight()) * rcp_src_height}); + + m_time.Update(std::array{static_cast(options.m_time_elapsed)}); + m_layer.Update(std::array{static_cast(options.m_source_layer)}); + m_z_depth_near.Update(std::array{options.m_depth_near}); + m_z_depth_far.Update(std::array{options.m_depth_far}); + m_output_scale.Update(std::array{last_pass_output_scale}); +} + +std::array BuiltinUniforms::GetOptions() const +{ + return {&m_prev_resolution, &m_prev_rect, &m_src_resolution, &m_window_resolution, + &m_src_rect, &m_time, &m_layer, &m_z_depth_near, + &m_z_depth_far, &m_output_scale}; +} +} // namespace VideoCommon::PE diff --git a/Source/Core/VideoCommon/PEShaderSystem/Runtime/BuiltinUniforms.h b/Source/Core/VideoCommon/PEShaderSystem/Runtime/BuiltinUniforms.h new file mode 100644 index 000000000000..f85e27b103d3 --- /dev/null +++ b/Source/Core/VideoCommon/PEShaderSystem/Runtime/BuiltinUniforms.h @@ -0,0 +1,41 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include + +#include "VideoCommon/PEShaderSystem/Runtime/PEShaderApplyOptions.h" +#include "VideoCommon/PEShaderSystem/Runtime/PEShaderOption.h" +#include "VideoCommon/PEShaderSystem/Runtime/RuntimeShaderImpl.h" + +class AbstractTexture; +class ShaderCode; + +namespace VideoCommon::PE +{ +class BuiltinUniforms +{ +public: + BuiltinUniforms(); + std::size_t Size() const; + void WriteToMemory(u8*& buffer) const; + void WriteShaderUniforms(ShaderCode& shader_source) const; + void Update(const ShaderApplyOptions& options, const AbstractTexture* last_pass_texture, + float last_pass_output_scale); + +private: + std::array GetOptions() const; + RuntimeFloatOption<4> m_prev_resolution; + RuntimeFloatOption<4> m_prev_rect; + RuntimeFloatOption<4> m_src_resolution; + RuntimeFloatOption<4> m_window_resolution; + RuntimeFloatOption<4> m_src_rect; + RuntimeIntOption<1> m_time; + RuntimeIntOption<1> m_layer; + RuntimeFloatOption<1> m_z_depth_near; + RuntimeFloatOption<1> m_z_depth_far; + RuntimeFloatOption<1> m_output_scale; +}; +} // namespace VideoCommon::PE diff --git a/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEBaseShader.cpp b/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEBaseShader.cpp new file mode 100644 index 000000000000..28e7fc741adb --- /dev/null +++ b/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEBaseShader.cpp @@ -0,0 +1,132 @@ +#include "VideoCommon/PEShaderSystem/Runtime/PEBaseShader.h" + +#include + +#include + +#include "Common/FileUtil.h" +#include "Common/MsgHandler.h" +#include "VideoCommon/PEShaderSystem/Runtime/PEShaderOption.h" + +#include "VideoCommon/AbstractFramebuffer.h" +#include "VideoCommon/AbstractPipeline.h" +#include "VideoCommon/AbstractShader.h" +#include "VideoCommon/AbstractTexture.h" +#include "VideoCommon/FramebufferShaderGen.h" +#include "VideoCommon/RenderBase.h" +#include "VideoCommon/ShaderGenCommon.h" +#include "VideoCommon/VertexManagerBase.h" +#include "VideoCommon/VideoCommon.h" +#include "VideoCommon/VideoConfig.h" + +namespace VideoCommon::PE +{ +bool BaseShader::UpdateConfig(const ShaderConfig& config) +{ + const bool update_static_options = config.m_changes != m_last_static_option_change_count; + const bool update_compile_options = + config.m_compiletime_changes != m_last_compile_option_change_count; + if (!update_static_options && !update_compile_options) + return true; + + m_last_static_option_change_count = config.m_changes; + m_last_compile_option_change_count = config.m_compiletime_changes; + + if (update_compile_options) + { + if (!RecompilePasses(config)) + { + return false; + } + } + + u32 i = 0; + for (const auto& config_option : config.m_options) + { + auto&& option = m_options[i]; + option->Update(config_option); + i++; + } + + return true; +} + +void BaseShader::CreateOptions(const ShaderConfig& config) +{ + m_last_static_option_change_count = config.m_changes; + m_last_compile_option_change_count = config.m_compiletime_changes; + m_options.clear(); + std::size_t buffer_size = m_builtin_uniforms.Size(); + for (const auto& config_option : config.m_options) + { + auto option = ShaderOption::Create(config_option); + buffer_size += option->Size(); + m_options.push_back(std::move(option)); + } + const auto remainder = buffer_size % 16; + + m_uniform_staging_buffer.resize(buffer_size + remainder * sizeof(float)); +} + +void BaseShader::UpdatePassInputs(const AbstractTexture* previous_texture) +{ + const auto update_inputs = [this](const BaseShaderPass& pass, + const AbstractTexture* previous_pass_texture) { + for (auto&& input : pass.m_inputs) + { + input->Update(previous_pass_texture, GetPasses()); + } + }; + + if (m_passes.size() == 1) + { + update_inputs(*m_passes[0], previous_texture); + } + + for (std::size_t i = 1; i < m_passes.size(); i++) + { + update_inputs(*m_passes[i], m_passes[i - 1]->m_output_texture.get()); + } +} + +std::vector BaseShader::GetPasses() const +{ + std::vector passes; + passes.reserve(m_passes.size()); + for (const auto& pass : m_passes) + { + passes.push_back(pass.get()); + } + return passes; +} + +void BaseShader::UploadUniformBuffer() +{ + u8* buffer = m_uniform_staging_buffer.data(); + std::memset(buffer, 0, m_uniform_staging_buffer.size()); + m_builtin_uniforms.WriteToMemory(buffer); + for (const auto& option : m_options) + { + option->WriteToMemory(buffer); + } + + g_vertex_manager->UploadUtilityUniforms( + m_uniform_staging_buffer.data(), static_cast(buffer - m_uniform_staging_buffer.data())); +} + +void BaseShader::PrepareUniformHeader(ShaderCode& shader_source) const +{ + if (g_ActiveConfig.backend_info.api_type == APIType::D3D) + shader_source.Write("cbuffer PSBlock : register(b0) {{\n"); + else + shader_source.Write("UBO_BINDING(std140, 1) uniform PSBlock {{\n"); + + m_builtin_uniforms.WriteShaderUniforms(shader_source); + + for (const auto& option : m_options) + { + option->WriteShaderUniforms(shader_source); + } + shader_source.Write("}};\n\n"); +} +} // namespace VideoCommon::PE diff --git a/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEBaseShader.h b/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEBaseShader.h new file mode 100644 index 000000000000..a6c7e3522fd2 --- /dev/null +++ b/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEBaseShader.h @@ -0,0 +1,57 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +#include "Common/CommonTypes.h" +#include "VideoCommon/AbstractShader.h" +#include "VideoCommon/PEShaderSystem/Config/PEShaderConfig.h" +#include "VideoCommon/PEShaderSystem/Runtime/BuiltinUniforms.h" +#include "VideoCommon/PEShaderSystem/Runtime/PEBaseShaderPass.h" +#include "VideoCommon/PEShaderSystem/Runtime/PEShaderApplyOptions.h" + +class AbstractTexture; +class ShaderCode; + +namespace VideoCommon::PE +{ +class ShaderOption; + +class BaseShader +{ +public: + BaseShader() = default; + virtual ~BaseShader() = default; + BaseShader(const BaseShader&) = delete; + BaseShader(BaseShader&&) = default; + BaseShader& operator=(const BaseShader&) = delete; + BaseShader& operator=(BaseShader&&) = default; + + bool UpdateConfig(const ShaderConfig& config); + void CreateOptions(const ShaderConfig& config); + virtual bool CreatePasses(const ShaderConfig& config) = 0; + virtual bool CreateAllPassOutputTextures(u32 width, u32 height, u32 layers, AbstractTextureFormat format) = 0; + virtual bool RebuildPipeline(AbstractTextureFormat format, u32 layers) = 0; + void UpdatePassInputs(const AbstractTexture* previous_texture); + virtual void Apply(bool skip_final_copy, const ShaderApplyOptions& options, + const AbstractTexture* prev_pass_texture, float prev_pass_output_scale) = 0; + + std::vector GetPasses() const; + virtual bool SupportsDirectWrite() const = 0; + +protected: + virtual bool RecompilePasses(const ShaderConfig& config) = 0; + void UploadUniformBuffer(); + void PrepareUniformHeader(ShaderCode& shader_source) const; + std::vector m_uniform_staging_buffer; + std::vector> m_passes; + std::vector> m_options; + BuiltinUniforms m_builtin_uniforms; + u32 m_last_static_option_change_count = 0; + u32 m_last_compile_option_change_count = 0; +}; +} // namespace VideoCommon::PE diff --git a/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEBaseShaderPass.cpp b/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEBaseShaderPass.cpp new file mode 100644 index 000000000000..41ab9d71d84e --- /dev/null +++ b/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEBaseShaderPass.cpp @@ -0,0 +1,53 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "VideoCommon/PEShaderSystem/Runtime/PEBaseShaderPass.h" + +#include "Common/CommonTypes.h" +#include "VideoCommon/ShaderGenCommon.h" + +namespace VideoCommon::PE +{ +void BaseShaderPass::WriteShaderIndices(ShaderCode& shader_source) const +{ + // Figure out which input indices map to color/depth/previous buffers. + // If any of these buffers is not bound, defaults of zero are fine here, + // since that buffer may not even be used by the shader. + u32 color_buffer_index = 0; + u32 depth_buffer_index = 0; + u32 prev_pass_output_index = 0; + u32 prev_shader_output_index = 0; + for (auto&& input : m_inputs) + { + switch (input->GetType()) + { + case InputType::ColorBuffer: + color_buffer_index = input->GetTextureUnit(); + break; + + case InputType::DepthBuffer: + depth_buffer_index = input->GetTextureUnit(); + break; + + case InputType::PreviousPassOutput: + prev_pass_output_index = input->GetTextureUnit(); + break; + + case InputType::PreviousShaderOutput: + prev_shader_output_index = input->GetTextureUnit(); + break; + + default: + break; + } + } + + // Hook the discovered indices up to macros. + // This is to support the SampleDepth, SamplePrev, etc. macros. + shader_source.Write("#define COLOR_BUFFER_INPUT_INDEX {}\n", color_buffer_index); + shader_source.Write("#define DEPTH_BUFFER_INPUT_INDEX {}\n", depth_buffer_index); + shader_source.Write("#define PREV_PASS_OUTPUT_INPUT_INDEX {}\n", prev_pass_output_index); + shader_source.Write("#define PREV_SHADER_OUTPUT_INPUT_INDEX {}\n", prev_shader_output_index); +} +} diff --git a/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEBaseShaderPass.h b/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEBaseShaderPass.h new file mode 100644 index 000000000000..e86c722dc026 --- /dev/null +++ b/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEBaseShaderPass.h @@ -0,0 +1,27 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include + +#include "VideoCommon/AbstractTexture.h" +#include "VideoCommon/PEShaderSystem/Runtime/PEShaderInput.h" + +class ShaderCode; + +namespace VideoCommon::PE +{ +struct BaseShaderPass +{ + std::vector> m_inputs; + + std::unique_ptr m_output_texture; + float m_output_scale; + + void WriteShaderIndices(ShaderCode& shader_source) const; +}; +} // namespace VideoCommon::PE diff --git a/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEComputeShader.cpp b/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEComputeShader.cpp new file mode 100644 index 000000000000..98cb3cb3f841 --- /dev/null +++ b/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEComputeShader.cpp @@ -0,0 +1,398 @@ +#include "VideoCommon/PEShaderSystem/Runtime/PEComputeShader.h" + +#include + +#include + +#include "Common/FileUtil.h" +#include "Common/MsgHandler.h" +#include "VideoCommon/PEShaderSystem/Runtime/PEShaderOption.h" + +#include "VideoCommon/AbstractFramebuffer.h" +#include "VideoCommon/AbstractPipeline.h" +#include "VideoCommon/AbstractShader.h" +#include "VideoCommon/AbstractTexture.h" +#include "VideoCommon/FramebufferShaderGen.h" +#include "VideoCommon/RenderBase.h" +#include "VideoCommon/ShaderGenCommon.h" +#include "VideoCommon/VertexManagerBase.h" +#include "VideoCommon/VideoCommon.h" +#include "VideoCommon/VideoConfig.h" + +namespace VideoCommon::PE +{ +bool ComputeShader::CreatePasses(const ShaderConfig& config) +{ + m_passes.clear(); + for (const auto& config_pass : config.m_passes) + { + bool should_skip_pass = false; + if (config_pass.m_dependent_option != "") + { + for (const auto& option : config.m_options) + { + if (IsUnsatisfiedDependency(option, config_pass.m_dependent_option)) + { + should_skip_pass = true; + break; + } + } + } + + if (should_skip_pass) + { + continue; + } + auto pass = std::make_unique(); + + pass->m_inputs.reserve(config_pass.m_inputs.size()); + for (const auto& config_input : config_pass.m_inputs) + { + auto input = ShaderInput::Create(config_input); + if (!input) + return false; + + pass->m_inputs.push_back(std::move(input)); + } + + pass->m_output_scale = config_pass.m_output_scale; + pass->m_compute_shader = CompileShader(config, *pass, config_pass); + if (!pass->m_compute_shader) + { + config.m_runtime_info->SetError(true); + return false; + } + + m_passes.push_back(std::move(pass)); + } + + if (m_passes.empty()) + return false; + + return true; +} + +bool ComputeShader::CreateAllPassOutputTextures(u32 width, u32 height, u32 layers, + AbstractTextureFormat format) +{ + std::size_t i = 0; + for (auto& pass : m_passes) + { + auto* native_pass = static_cast(pass.get()); + u32 output_width; + u32 output_height; + if (native_pass->m_output_scale < 0.0f) + { + const float native_scale = -native_pass->m_output_scale; + const int native_width = width * EFB_WIDTH / g_renderer->GetTargetWidth(); + const int native_height = height * EFB_HEIGHT / g_renderer->GetTargetHeight(); + output_width = std::max( + static_cast(std::round((static_cast(native_width) * native_scale))), 1u); + output_height = std::max( + static_cast(std::round((static_cast(native_height) * native_scale))), 1u); + } + else + { + output_width = std::max(1u, static_cast(width * native_pass->m_output_scale)); + output_height = std::max(1u, static_cast(height * native_pass->m_output_scale)); + } + + native_pass->m_output_texture.reset(); + native_pass->m_output_texture = + g_renderer->CreateTexture(TextureConfig(output_width, output_height, 1, layers, 1, format, + AbstractTextureFlag_ComputeImage), + fmt::format("Pass {}", i)); + if (!native_pass->m_output_texture) + { + PanicAlertFmt("Failed to create texture for PE shader pass"); + return false; + } + i++; + } + + return true; +} + +bool ComputeShader::RebuildPipeline(AbstractTextureFormat format, u32 layers) +{ + // Compute shaders don't have any pipelines + return true; +} + +void ComputeShader::Apply(bool skip_final_copy, const ShaderApplyOptions& options, + const AbstractTexture* prev_shader_texture, float prev_pass_output_scale) +{ + const auto parse_inputs = [&](const ComputeShaderPass& pass, + const AbstractTexture* prev_pass_texture) { + for (auto&& input : pass.m_inputs) + { + switch (input->GetType()) + { + case InputType::ColorBuffer: + g_renderer->SetTexture(input->GetTextureUnit(), options.m_source_color_tex); + break; + + case InputType::DepthBuffer: + g_renderer->SetTexture(input->GetTextureUnit(), options.m_source_depth_tex); + break; + + case InputType::ExternalImage: + case InputType::PassOutput: + g_renderer->SetTexture(input->GetTextureUnit(), input->GetTexture()); + break; + case InputType::PreviousPassOutput: + g_renderer->SetTexture(input->GetTextureUnit(), prev_pass_texture); + break; + case InputType::PreviousShaderOutput: + g_renderer->SetTexture(input->GetTextureUnit(), prev_shader_texture); + break; + } + + g_renderer->SetSamplerState(input->GetTextureUnit(), input->GetSamplerState()); + } + }; + + const AbstractTexture* last_pass_texture = prev_shader_texture; + float last_pass_output_scale = prev_pass_output_scale; + for (const auto& pass : m_passes) + { + auto* native_pass = static_cast(pass.get()); + parse_inputs(*native_pass, last_pass_texture); + + m_builtin_uniforms.Update(options, last_pass_texture, last_pass_output_scale); + UploadUniformBuffer(); + + g_renderer->SetComputeImageTexture(native_pass->m_output_texture.get(), false, true); + + // Should these be exposed? + const u32 working_group_size = 8; + g_renderer->DispatchComputeShader( + native_pass->m_compute_shader.get(), + native_pass->m_output_texture->GetWidth() / working_group_size, + native_pass->m_output_texture->GetHeight() / working_group_size, 1); + + last_pass_texture = native_pass->m_output_texture.get(); + last_pass_output_scale = native_pass->m_output_scale; + } +} + +bool ComputeShader::RecompilePasses(const ShaderConfig& config) +{ + for (std::size_t i = 0; i < m_passes.size(); i++) + { + auto& pass = m_passes[i]; + auto* native_pass = static_cast(pass.get()); + auto& config_pass = config.m_passes[i]; + + native_pass->m_compute_shader = CompileShader(config, *native_pass, config_pass); + if (!native_pass->m_compute_shader) + { + config.m_runtime_info->SetError(true); + return false; + } + } + + return true; +} + +std::unique_ptr +ComputeShader::CompileShader(const ShaderConfig& config, const ComputeShaderPass& pass, + const ShaderConfigPass& config_pass) const +{ + ShaderCode shader_source; + PrepareUniformHeader(shader_source); + ShaderHeader(shader_source, pass); + + std::string shader_body; + File::ReadFileToString(config.m_shader_path, shader_body); + shader_body = ReplaceAll(shader_body, "\r\n", "\n"); + shader_body = ReplaceAll(shader_body, "{", "{{"); + shader_body = ReplaceAll(shader_body, "}", "}}"); + + shader_source.Write(shader_body); + shader_source.Write("\n"); + ShaderFooter(shader_source, config_pass); + return g_renderer->CreateShaderFromSource(ShaderStage::Compute, shader_source.GetBuffer(), + fmt::format("{} compute", config.m_name)); +} + +void ComputeShader::ShaderHeader(ShaderCode& shader_source, const ComputeShaderPass& pass) const +{ + if (g_ActiveConfig.backend_info.api_type == APIType::D3D) + { + // Rename main, since we need to set up globals + shader_source.Write(R"( +#define HLSL 1 +#define main real_main + +Texture2DArray samp[{0}] : register(t0); +SamplerState samp_ss[{0}] : register(s0); +RWTexture2DArray output_image : register(u0); + +static float3 v_tex0; +static float3 v_tex1; + +// Type aliases. +#define mat2 float2x2 +#define mat3 float3x3 +#define mat4 float4x4 +#define mat4x3 float4x3 + +// function aliases. (see https://anteru.net/blog/2016/mapping-between-HLSL-and-GLSL/) +#define dFdx ddx +#define dFdxCoarse ddx_coarse +#define dFdxFine ddx_fine +#define dFdy ddy +#define dFdyCoarse ddy_coarse +#define dFdyFine ddy_fine +#define interpolateAtCentroid EvaluateAttributeAtCentroid +#define interpolateAtSample EvaluateAttributeAtSample +#define interpolateAtOffset EvaluateAttributeSnapped +#define fract frac +#define mix lerp +#define fma mad + +#define GROUP_MEMORY_BARRIER_WITH_SYNC GroupMemoryBarrierWithGroupSync(); +#define GROUP_SHARED groupshared + +// Wrappers for sampling functions. +float4 SampleInputLocation(int value, float2 location) {{ return samp[value].SampleLevel(samp_ss[value], float3(location, float(u_layer)), 0); }} +void SetOutput(float4 color, int3 pixel_coord) {{ output_image[pixel_coord] = color; }} + +int2 SampleInputSize(int value, int lod) +{{ + uint width; + uint height; + uint elements; + uint miplevels; + samp[value].GetDimensions(lod, width, height, elements, miplevels); + return int2(width, height); +}} + +)", + pass.m_inputs.size()); + } + else + { + shader_source.Write(R"( +#define GLSL 1 +#define main real_main + +// Type aliases. +#define float2x2 mat2 +#define float3x3 mat3 +#define float4x4 mat4 +#define float4x3 mat4x3 + +// Utility functions. +float saturate(float x) {{ return clamp(x, 0.0f, 1.0f); }} +float2 saturate(float2 x) {{ return clamp(x, float2(0.0f, 0.0f), float2(1.0f, 1.0f)); }} +float3 saturate(float3 x) {{ return clamp(x, float3(0.0f, 0.0f, 0.0f), float3(1.0f, 1.0f, 1.0f)); }} +float4 saturate(float4 x) {{ return clamp(x, float4(0.0f, 0.0f, 0.0f, 0.0f), float4(1.0f, 1.0f, 1.0f, 1.0f)); }} + +// Flipped multiplication order because GLSL matrices use column vectors. +float2 mul(float2x2 m, float2 v) {{ return (v * m); }} +float3 mul(float3x3 m, float3 v) {{ return (v * m); }} +float4 mul(float4x3 m, float3 v) {{ return (v * m); }} +float4 mul(float4x4 m, float4 v) {{ return (v * m); }} +float2 mul(float2 v, float2x2 m) {{ return (m * v); }} +float3 mul(float3 v, float3x3 m) {{ return (m * v); }} +float3 mul(float4 v, float4x3 m) {{ return (m * v); }} +float4 mul(float4 v, float4x4 m) {{ return (m * v); }} + +float4x3 mul(float4x3 m, float3x3 m2) {{ return (m2 * m); }} + +SAMPLER_BINDING(0) uniform sampler2DArray samp[{0}]; +IMAGE_BINDING(rgba8, 0) uniform writeonly image2DArray output_image; + +#define GROUP_MEMORY_BARRIER_WITH_SYNC memoryBarrierShared(); barrier(); +#define GROUP_SHARED shared + +// Wrappers for sampling functions. +float4 SampleInputLocation(int value, float2 location) {{ return texture(samp[value], float3(location, float(u_layer))); }} +void SetOutput(float4 color, int3 pixel_coord) {{ imageStore(output_image, pixel_coord, color); }} + +ivec3 SampleInputSize(int value, int lod) {{ return textureSize(samp[value], lod); }} +)", + pass.m_inputs.size()); + } + + pass.WriteShaderIndices(shader_source); + + for (const auto& option : m_options) + { + option->WriteShaderConstants(shader_source); + } + + shader_source.Write(R"( + +float2 GetResolution() {{ return prev_resolution.xy; }} +float2 GetInvResolution() {{ return prev_resolution.zw; }} +float2 GetWindowResolution() {{ return window_resolution.xy; }} +float2 GetInvWindowResolution() {{ return window_resolution.zw; }} + +float2 GetPrevResolution() {{ return prev_resolution.xy; }} +float2 GetInvPrevResolution() {{ return prev_resolution.zw; }} +float2 GetPrevRectOrigin() {{ return prev_rect.xy; }} +float2 GetPrevRectSize() {{ return prev_rect.zw; }} + +float2 GetSrcResolution() {{ return src_resolution.xy; }} +float2 GetInvSrcResolution() {{ return src_resolution.zw; }} +float2 GetSrcRectOrigin() {{ return src_rect.xy; }} +float2 GetSrcRectSize() {{ return src_rect.zw; }} + +float GetLayer() {{ return u_layer; }} +float GetTime() {{ return u_time; }} + +#define GetOption(x) (x) +#define OptionEnabled(x) (x) + +)"); +} + +void ComputeShader::ShaderFooter(ShaderCode& shader_source, + const ShaderConfigPass& config_pass) const +{ + if (g_ActiveConfig.backend_info.api_type == APIType::D3D) + { + shader_source.Write("#undef main\n"); + shader_source.Write("[numthreads(8, 8, 1)]\n"); + shader_source.Write("void main(uint3 workGroupID : SV_GroupId,\n"); + shader_source.Write("\tuint3 localInvocationID : SV_GroupThreadID,\n"); + shader_source.Write("\tuint3 globalInvocationID : SV_DispatchThreadID)\n"); + shader_source.Write("{{\n"); + + if (config_pass.m_entry_point == "main") + { + shader_source.Write("\treal_main(workGroupID, localInvocationID, globalInvocationID);\n"); + } + else if (!config_pass.m_entry_point.empty()) + { + shader_source.Write("\t{}(workGroupID, localInvocationID, globalInvocationID);\n", + config_pass.m_entry_point); + } + shader_source.Write("}}\n"); + } + else + { + shader_source.Write("#undef main\n"); + shader_source.Write("layout(local_size_x = 8, local_size_y = 8) in;\n"); + + shader_source.Write("void main()\n"); + shader_source.Write("{{\n"); + + if (config_pass.m_entry_point == "main") + { + shader_source.Write( + "\treal_main(gl_WorkGroupID, gl_LocalInvocationID, gl_GlobalInvocationID);\n"); + } + else if (!config_pass.m_entry_point.empty()) + { + shader_source.Write("\t{}(gl_WorkGroupID, gl_LocalInvocationID, gl_GlobalInvocationID);\n", + config_pass.m_entry_point); + } + + shader_source.Write("}}\n"); + } +} +} // namespace VideoCommon::PE diff --git a/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEComputeShader.h b/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEComputeShader.h new file mode 100644 index 000000000000..ec59bf77cb23 --- /dev/null +++ b/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEComputeShader.h @@ -0,0 +1,46 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include "VideoCommon/PEShaderSystem/Runtime/PEBaseShader.h" + +#include +#include + +#include "Common/CommonTypes.h" +#include "VideoCommon/AbstractShader.h" +#include "VideoCommon/PEShaderSystem/Config/PEShaderConfig.h" +#include "VideoCommon/PEShaderSystem/Runtime/BuiltinUniforms.h" +#include "VideoCommon/PEShaderSystem/Runtime/PEComputeShaderPass.h" +#include "VideoCommon/PEShaderSystem/Runtime/PEShaderApplyOptions.h" + +class AbstractTexture; +class ShaderCode; + +namespace VideoCommon::PE +{ +class ShaderOption; + +class ComputeShader final : public BaseShader +{ +public: + bool CreatePasses(const ShaderConfig& config) override; + bool CreateAllPassOutputTextures(u32 width, u32 height, u32 layers, + AbstractTextureFormat format) override; + bool RebuildPipeline(AbstractTextureFormat format, u32 layers) override; + void Apply(bool skip_final_copy, const ShaderApplyOptions& options, + const AbstractTexture* prev_pass_texture, float prev_pass_output_scale) override; + + bool SupportsDirectWrite() const override { return false; } + +private: + bool RecompilePasses(const ShaderConfig& config) override; + std::unique_ptr CompileShader(const ShaderConfig& config, + const ComputeShaderPass& pass, + const ShaderConfigPass& config_pass) const; + void ShaderFooter(ShaderCode& shader_source, const ShaderConfigPass& config_pass) const; + void ShaderHeader(ShaderCode& shader_source, const ComputeShaderPass& pass) const; +}; +} // namespace VideoCommon::PE diff --git a/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEComputeShaderPass.h b/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEComputeShaderPass.h new file mode 100644 index 000000000000..fe813ea069de --- /dev/null +++ b/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEComputeShaderPass.h @@ -0,0 +1,19 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include + +#include "VideoCommon/AbstractShader.h" +#include "VideoCommon/PEShaderSystem/Runtime/PEBaseShaderPass.h" +#include "VideoCommon/PEShaderSystem/Runtime/PEShaderInput.h" + +namespace VideoCommon::PE +{ +struct ComputeShaderPass final : public BaseShaderPass +{ + std::unique_ptr m_compute_shader; +}; +} // namespace VideoCommon::PE diff --git a/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEShader.cpp b/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEShader.cpp new file mode 100644 index 000000000000..d382825d95f6 --- /dev/null +++ b/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEShader.cpp @@ -0,0 +1,639 @@ +#include "VideoCommon/PEShaderSystem/Runtime/PEShader.h" + +#include + +#include + +#include "Common/FileUtil.h" +#include "Common/MsgHandler.h" +#include "VideoCommon/PEShaderSystem/Runtime/PEShaderOption.h" + +#include "VideoCommon/AbstractFramebuffer.h" +#include "VideoCommon/AbstractPipeline.h" +#include "VideoCommon/AbstractShader.h" +#include "VideoCommon/AbstractTexture.h" +#include "VideoCommon/FramebufferShaderGen.h" +#include "VideoCommon/RenderBase.h" +#include "VideoCommon/ShaderGenCommon.h" +#include "VideoCommon/VertexManagerBase.h" +#include "VideoCommon/VideoCommon.h" +#include "VideoCommon/VideoConfig.h" + +namespace VideoCommon::PE +{ +bool VertexPixelShader::CreatePasses(const ShaderConfig& config) +{ + if (g_ActiveConfig.backend_info.bSupportsGeometryShaders && !m_geometry_shader) + { + m_geometry_shader = CompileGeometryShader(config); + if (!m_geometry_shader) + return false; + } + + std::shared_ptr vertex_shader = CompileVertexShader(config); + if (!vertex_shader) + { + config.m_runtime_info->SetError(true); + return false; + } + + m_passes.clear(); + for (const auto& config_pass : config.m_passes) + { + bool should_skip_pass = false; + if (config_pass.m_dependent_option != "") + { + for (const auto& option : config.m_options) + { + if (IsUnsatisfiedDependency(option, config_pass.m_dependent_option)) + { + should_skip_pass = true; + break; + } + } + } + + if (should_skip_pass) + { + continue; + } + auto pass = std::make_unique(); + + pass->m_inputs.reserve(config_pass.m_inputs.size()); + for (const auto& config_input : config_pass.m_inputs) + { + auto input = ShaderInput::Create(config_input); + if (!input) + return false; + + pass->m_inputs.push_back(std::move(input)); + } + + pass->m_output_scale = config_pass.m_output_scale; + pass->m_vertex_shader = vertex_shader; + pass->m_pixel_shader = CompilePixelShader(config, *pass, config_pass); + if (!pass->m_pixel_shader) + { + config.m_runtime_info->SetError(true); + return false; + } + + m_passes.push_back(std::move(pass)); + } + + if (m_passes.empty()) + return false; + + return true; +} + +bool VertexPixelShader::CreateAllPassOutputTextures(u32 width, u32 height, u32 layers, + AbstractTextureFormat format) +{ + std::size_t i = 0; + for (auto& pass : m_passes) + { + auto* native_pass = static_cast(pass.get()); + u32 output_width; + u32 output_height; + if (pass->m_output_scale < 0.0f) + { + const float native_scale = -pass->m_output_scale; + const int native_width = width * EFB_WIDTH / g_renderer->GetTargetWidth(); + const int native_height = height * EFB_HEIGHT / g_renderer->GetTargetHeight(); + output_width = std::max( + static_cast(std::round((static_cast(native_width) * native_scale))), 1u); + output_height = std::max( + static_cast(std::round((static_cast(native_height) * native_scale))), 1u); + } + else + { + output_width = std::max(1u, static_cast(width * pass->m_output_scale)); + output_height = std::max(1u, static_cast(height * pass->m_output_scale)); + } + + native_pass->m_output_framebuffer.reset(); + pass->m_output_texture.reset(); + pass->m_output_texture = + g_renderer->CreateTexture(TextureConfig(output_width, output_height, 1, layers, 1, format, + AbstractTextureFlag_RenderTarget), + fmt::format("Pass {}", i)); + if (!pass->m_output_texture) + { + PanicAlertFmt("Failed to create texture for PE shader pass"); + return false; + } + native_pass->m_output_framebuffer = + g_renderer->CreateFramebuffer(pass->m_output_texture.get(), nullptr); + if (!native_pass->m_output_framebuffer) + { + PanicAlertFmt("Failed to create framebuffer for PE shader pass"); + return false; + } + i++; + } + + return true; +} + +bool VertexPixelShader::RebuildPipeline(AbstractTextureFormat format, u32 layers) +{ + AbstractPipelineConfig config = {}; + config.geometry_shader = layers > 1 ? m_geometry_shader.get() : nullptr; + config.rasterization_state = RenderState::GetNoCullRasterizationState(PrimitiveType::Triangles); + config.depth_state = RenderState::GetNoDepthTestingDepthState(); + config.blending_state = RenderState::GetNoBlendingBlendState(); + config.framebuffer_state = RenderState::GetColorFramebufferState(format); + config.usage = AbstractPipelineUsage::Utility; + + m_last_pipeline_format = format; + m_last_pipeline_layers = layers; + + for (auto& pass : m_passes) + { + auto* native_pass = static_cast(pass.get()); + config.vertex_shader = native_pass->m_vertex_shader.get(); + config.pixel_shader = native_pass->m_pixel_shader.get(); + native_pass->m_pipeline = g_renderer->CreatePipeline(config); + if (!native_pass->m_pipeline) + { + PanicAlertFmt("Failed to compile pipeline for PE shader pass"); + return false; + } + } + + return true; +} + +void VertexPixelShader::Apply(bool skip_final_copy, const ShaderApplyOptions& options, + const AbstractTexture* prev_shader_texture, + float prev_pass_output_scale) +{ + const auto parse_inputs = [&](const ShaderPass& pass, const AbstractTexture* prev_pass_texture) { + for (auto&& input : pass.m_inputs) + { + switch (input->GetType()) + { + case InputType::ColorBuffer: + g_renderer->SetTexture(input->GetTextureUnit(), options.m_source_color_tex); + break; + + case InputType::DepthBuffer: + g_renderer->SetTexture(input->GetTextureUnit(), options.m_source_depth_tex); + break; + + case InputType::ExternalImage: + case InputType::PassOutput: + g_renderer->SetTexture(input->GetTextureUnit(), input->GetTexture()); + break; + case InputType::PreviousPassOutput: + g_renderer->SetTexture(input->GetTextureUnit(), prev_pass_texture); + break; + case InputType::PreviousShaderOutput: + g_renderer->SetTexture(input->GetTextureUnit(), prev_shader_texture); + break; + } + + g_renderer->SetSamplerState(input->GetTextureUnit(), input->GetSamplerState()); + } + }; + + const AbstractTexture* last_pass_texture = prev_shader_texture; + float last_pass_output_scale = prev_pass_output_scale; + for (std::size_t i = 0; i < m_passes.size() - 1; i++) + { + auto& pass = m_passes[i]; + auto* native_pass = static_cast(pass.get()); + g_renderer->SetAndDiscardFramebuffer(native_pass->m_output_framebuffer.get()); + g_renderer->SetPipeline(native_pass->m_pipeline.get()); + g_renderer->SetViewportAndScissor(g_renderer->ConvertFramebufferRectangle( + native_pass->m_output_framebuffer->GetRect(), native_pass->m_output_framebuffer.get())); + + parse_inputs(*native_pass, last_pass_texture); + + m_builtin_uniforms.Update(options, last_pass_texture, last_pass_output_scale); + UploadUniformBuffer(); + + g_renderer->Draw(0, 3); + native_pass->m_output_framebuffer->GetColorAttachment()->FinishedRendering(); + + last_pass_texture = pass->m_output_texture.get(); + last_pass_output_scale = pass->m_output_scale; + } + + const auto& pass = m_passes[m_passes.size() - 1]; + const auto* native_pass = static_cast(pass.get()); + if (skip_final_copy) + { + g_renderer->SetFramebuffer(options.m_dest_fb); + g_renderer->SetPipeline(native_pass->m_pipeline.get()); + g_renderer->SetViewportAndScissor( + g_renderer->ConvertFramebufferRectangle(options.m_dest_rect, options.m_dest_fb)); + + parse_inputs(*native_pass, last_pass_texture); + + m_builtin_uniforms.Update(options, last_pass_texture, last_pass_output_scale); + UploadUniformBuffer(); + + g_renderer->Draw(0, 3); + } + else + { + g_renderer->SetAndDiscardFramebuffer(native_pass->m_output_framebuffer.get()); + g_renderer->SetPipeline(native_pass->m_pipeline.get()); + g_renderer->SetViewportAndScissor(g_renderer->ConvertFramebufferRectangle( + native_pass->m_output_framebuffer->GetRect(), native_pass->m_output_framebuffer.get())); + + parse_inputs(*native_pass, last_pass_texture); + + m_builtin_uniforms.Update(options, last_pass_texture, last_pass_output_scale); + UploadUniformBuffer(); + + g_renderer->Draw(0, 3); + native_pass->m_output_framebuffer->GetColorAttachment()->FinishedRendering(); + } +} + +bool VertexPixelShader::RecompilePasses(const ShaderConfig& config) +{ + if (g_ActiveConfig.backend_info.bSupportsGeometryShaders && !m_geometry_shader) + { + m_geometry_shader = CompileGeometryShader(config); + if (!m_geometry_shader) + return false; + } + + std::shared_ptr vertex_shader = CompileVertexShader(config); + if (!vertex_shader) + { + config.m_runtime_info->SetError(true); + return false; + } + + for (std::size_t i = 0; i < m_passes.size(); i++) + { + auto& pass = m_passes[i]; + auto* native_pass = static_cast(pass.get()); + auto& config_pass = config.m_passes[i]; + + native_pass->m_vertex_shader = vertex_shader; + native_pass->m_pixel_shader = CompilePixelShader(config, *native_pass, config_pass); + if (!native_pass->m_pixel_shader) + { + config.m_runtime_info->SetError(true); + return false; + } + } + + if (!RebuildPipeline(m_last_pipeline_format, m_last_pipeline_layers)) + return false; + + return true; +} + +std::unique_ptr +VertexPixelShader::CompileGeometryShader(const ShaderConfig& config) const +{ + std::string source = FramebufferShaderGen::GeneratePassthroughGeometryShader(2, 0); + return g_renderer->CreateShaderFromSource(ShaderStage::Geometry, std::move(source), + fmt::format("{} gs", config.m_name)); +} + +std::shared_ptr +VertexPixelShader::CompileVertexShader(const ShaderConfig& config) const +{ + ShaderCode shader_source; + PrepareUniformHeader(shader_source); + VertexShaderMain(shader_source); + + std::unique_ptr vs = g_renderer->CreateShaderFromSource( + ShaderStage::Vertex, shader_source.GetBuffer(), fmt::format("{} vs", config.m_name)); + if (!vs) + { + return nullptr; + } + + // convert from unique_ptr to shared_ptr, as many passes share one VS + return std::shared_ptr(vs.release()); +} + +std::unique_ptr +VertexPixelShader::CompilePixelShader(const ShaderConfig& config, const ShaderPass& pass, + const ShaderConfigPass& config_pass) const +{ + ShaderCode shader_source; + PrepareUniformHeader(shader_source); + PixelShaderHeader(shader_source, pass); + + std::string shader_body; + File::ReadFileToString(config.m_shader_path, shader_body); + shader_body = ReplaceAll(shader_body, "\r\n", "\n"); + shader_body = ReplaceAll(shader_body, "{", "{{"); + shader_body = ReplaceAll(shader_body, "}", "}}"); + + shader_source.Write(shader_body); + shader_source.Write("\n"); + PixelShaderFooter(shader_source, config_pass); + return g_renderer->CreateShaderFromSource(ShaderStage::Pixel, shader_source.GetBuffer(), + fmt::format("{} ps", config.m_name)); +} + +void VertexPixelShader::PixelShaderHeader(ShaderCode& shader_source, const ShaderPass& pass) const +{ + if (g_ActiveConfig.backend_info.api_type == APIType::D3D) + { + // Rename main, since we need to set up globals + shader_source.Write(R"( +#define HLSL 1 +#define main real_main +#define gl_FragCoord v_fragcoord + +Texture2DArray samp[{0}] : register(t0); +SamplerState samp_ss[{0}] : register(s0); + +static float3 v_tex0; +static float3 v_tex1; +static float4 v_fragcoord; +static float4 ocol0; + +// Type aliases. +#define mat2 float2x2 +#define mat3 float3x3 +#define mat4 float4x4 +#define mat4x3 float4x3 + +// function aliases. (see https://anteru.net/blog/2016/mapping-between-HLSL-and-GLSL/) +#define dFdx ddx +#define dFdxCoarse ddx_coarse +#define dFdxFine ddx_fine +#define dFdy ddy +#define dFdyCoarse ddy_coarse +#define dFdyFine ddy_fine +#define interpolateAtCentroid EvaluateAttributeAtCentroid +#define interpolateAtSample EvaluateAttributeAtSample +#define interpolateAtOffset EvaluateAttributeSnapped +#define fract frac +#define mix lerp +#define fma mad + +// Wrappers for sampling functions. +float4 SampleInput(int value) {{ return samp[value].Sample(samp_ss[value], float3(v_tex0.xy, float(u_layer))); }} +float4 SampleInputLocation(int value, float2 location) {{ return samp[value].Sample(samp_ss[value], float3(location, float(u_layer))); }} +float4 SampleInputLayer(int value, int layer) {{ return samp[value].Sample(samp_ss[value], float3(v_tex0.xy, float(layer))); }} +float4 SampleInputLocationLayer(int value, float2 location, int layer) {{ return samp[value].Sample(samp_ss[value], float3(location, float(layer))); }} + +float4 SampleInputLod(int value, float lod) {{ return samp[value].SampleLevel(samp_ss[value], float3(v_tex0.xy, float(u_layer)), lod); }} +float4 SampleInputLodLocation(int value, float lod, float2 location) {{ return samp[value].SampleLevel(samp_ss[value], float3(location, float(u_layer)), lod); }} +float4 SampleInputLodLayer(int value, float lod, int layer) {{ return samp[value].SampleLevel(samp_ss[value], float3(v_tex0.xy, float(layer)), lod); }} +float4 SampleInputLodLocationLayer(int value, float lod, float2 location, int layer) {{ return samp[value].SampleLevel(samp_ss[value], float3(location, float(layer)), lod); }} + +float4 SampleInputOffset(int value, int2 offset) {{ return samp[value].Sample(samp_ss[value], float3(v_tex0.xy, float(u_layer)), offset); }} +float4 SampleInputLocationOffset(int value, float2 location, int2 offset) {{ return samp[value].Sample(samp_ss[value], float3(location.xy, float(u_layer)), offset); }} +float4 SampleInputLayerOffset(int value, int layer, int2 offset) {{ return samp[value].Sample(samp_ss[value], float3(v_tex0.xy, float(layer)), offset); }} +float4 SampleInputLocationLayerOffset(int value, float2 location, int layer, int2 offset) {{ return samp[value].Sample(samp_ss[value], float3(location.xy, float(layer)), offset); }} + +float4 SampleInputLodOffset(int value, float lod, int2 offset) {{ return samp[value].SampleLevel(samp_ss[value], float3(v_tex0.xy, float(u_layer)), lod, offset); }} +float4 SampleInputLodLocationOffset(int value, float lod, float2 location, int2 offset) {{ return samp[value].SampleLevel(samp_ss[value], float3(location.xy, float(u_layer)), lod, offset); }} +float4 SampleInputLodLayerOffset(int value, float lod, int layer, int2 offset) {{ return samp[value].SampleLevel(samp_ss[value], float3(v_tex0.xy, float(layer)), lod, offset); }} +float4 SampleInputLodLocationLayerOffset(int value, float lod, float2 location, int layer, int2 offset) {{ return samp[value].SampleLevel(samp_ss[value], float3(location.xy, float(layer)), lod, offset); }} + +int2 SampleInputSize(int value, int lod) +{{ + uint width; + uint height; + uint elements; + uint miplevels; + samp[value].GetDimensions(lod, width, height, elements, miplevels); + return int2(width, height); +}} + +)", + pass.m_inputs.size()); + } + else + { + shader_source.Write(R"( +#define GLSL 1 + +// Type aliases. +#define float2x2 mat2 +#define float3x3 mat3 +#define float4x4 mat4 +#define float4x3 mat4x3 + +// Utility functions. +float saturate(float x) {{ return clamp(x, 0.0f, 1.0f); }} +float2 saturate(float2 x) {{ return clamp(x, float2(0.0f, 0.0f), float2(1.0f, 1.0f)); }} +float3 saturate(float3 x) {{ return clamp(x, float3(0.0f, 0.0f, 0.0f), float3(1.0f, 1.0f, 1.0f)); }} +float4 saturate(float4 x) {{ return clamp(x, float4(0.0f, 0.0f, 0.0f, 0.0f), float4(1.0f, 1.0f, 1.0f, 1.0f)); }} + +// Flipped multiplication order because GLSL matrices use column vectors. +float2 mul(float2x2 m, float2 v) {{ return (v * m); }} +float3 mul(float3x3 m, float3 v) {{ return (v * m); }} +float4 mul(float4x3 m, float3 v) {{ return (v * m); }} +float4 mul(float4x4 m, float4 v) {{ return (v * m); }} +float2 mul(float2 v, float2x2 m) {{ return (m * v); }} +float3 mul(float3 v, float3x3 m) {{ return (m * v); }} +float3 mul(float4 v, float4x3 m) {{ return (m * v); }} +float4 mul(float4 v, float4x4 m) {{ return (m * v); }} + +float4x3 mul(float4x3 m, float3x3 m2) {{ return (m2 * m); }} + +SAMPLER_BINDING(0) uniform sampler2DArray samp[{0}]; +VARYING_LOCATION(0) in float3 v_tex0; +VARYING_LOCATION(1) in float3 v_tex1; +FRAGMENT_OUTPUT_LOCATION(0) out float4 ocol0; + +// Wrappers for sampling functions. +float4 SampleInput(int value) {{ return texture(samp[value], float3(v_tex0.xy, float(u_layer))); }} +float4 SampleInputLocation(int value, float2 location) {{ return texture(samp[value], float3(location, float(u_layer))); }} +float4 SampleInputLayer(int value, int layer) {{ return texture(samp[value], float3(v_tex0.xy, float(layer))); }} +float4 SampleInputLocationLayer(int value, float2 location, int layer) {{ return texture(samp[value], float3(location, float(layer))); }} + +float4 SampleInputLod(int value, float lod) {{ return textureLod(samp[value], float3(v_tex0.xy, float(u_layer)), lod); }} +float4 SampleInputLodLocation(int value, float lod, float2 location) {{ return textureLod(samp[value], float3(location, float(u_layer)), lod); }} +float4 SampleInputLodLayer(int value, float lod, int layer) {{ return textureLod(samp[value], float3(v_tex0.xy, float(layer)), lod); }} +float4 SampleInputLodLocationLayer(int value, float lod, float2 location, int layer) {{ return textureLod(samp[value], float3(location, float(layer)), lod); }} + +// In Vulkan, offset can only be a compile time constant +// so instead of using a function, we use a macro +#define SampleInputOffset(value, offset) (textureOffset(samp[value], float3(v_tex0.xy, float(u_layer)), offset)) +#define SampleInputLocationOffset(value, location, offset) (textureOffset(samp[value], float3(location.xy, float(u_layer)), offset)) +#define SampleInputLayerOffset(value, layer, offset) (textureOffset(samp[value], float3(v_tex0.xy, float(layer)), offset)) +#define SampleInputLocationLayerOffset(value, location, layer, offset) (textureOffset(samp[value], float3(location.xy, float(layer)), offset)) + +#define SampleInputLodOffset(value, lod, offset) (textureLodOffset(samp[value], float3(v_tex0.xy, float(u_layer)), lod, offset)) +#define SampleInputLodLocationOffset(value, lod, location, offset) (textureLodOffset(samp[value], float3(location.xy, float(u_layer)), lod, offset)) +#define SampleInputLodLayerOffset(value, lod, layer, offset) (textureLodOffset(samp[value], float3(v_tex0.xy, float(layer)), lod, offset)) +#define SampleInputLodLocationLayerOffset(value, lod, location, layer, offset) (textureLodOffset(samp[value], float3(location.xy, float(layer)), lod, offset)) + +ivec3 SampleInputSize(int value, int lod) {{ return textureSize(samp[value], lod); }} +)", + pass.m_inputs.size()); + } + + if (g_ActiveConfig.backend_info.bSupportsReversedDepthRange) + shader_source.Write("#define DEPTH_VALUE(val) (val)\n"); + else + shader_source.Write("#define DEPTH_VALUE(val) (1.0 - (val))\n"); + + pass.WriteShaderIndices(shader_source); + + for (const auto& option : m_options) + { + option->WriteShaderConstants(shader_source); + } + + shader_source.Write(R"( + +// Convert z/w -> linear depth +// https://gist.github.com/kovrov/a26227aeadde77b78092b8a962bd1a91 +// http://dougrogers.blogspot.com/2013/02/how-to-derive-near-and-far-clip-plane.html +float ToLinearDepth(float depth) +{{ + // TODO: The depth values provided by the projection matrix + // produce invalid results, need to determine what the correct + // values are + //float NearZ = z_depth_near; + //float FarZ = z_depth_far; + + // For now just hardcode our near/far planes + float NearZ = 0.001f; + float FarZ = 1.0f; + const float A = (1.0f - (FarZ / NearZ)) / 2.0f; + const float B = (1.0f + (FarZ / NearZ)) / 2.0f; + return 1.0f / (A * depth + B); +}} + +// For backwards compatibility. +float4 Sample() {{ return SampleInput(PREV_PASS_OUTPUT_INPUT_INDEX); }} +float4 SampleLocation(float2 location) {{ return SampleInputLocation(PREV_PASS_OUTPUT_INPUT_INDEX, location); }} +float4 SampleLayer(int layer) {{ return SampleInputLayer(PREV_PASS_OUTPUT_INPUT_INDEX, layer); }} +float4 SamplePrev() {{ return SampleInput(PREV_PASS_OUTPUT_INPUT_INDEX); }} +float4 SamplePrevLocation(float2 location) {{ return SampleInputLocation(PREV_PASS_OUTPUT_INPUT_INDEX, location); }} +float SampleRawDepth() {{ return DEPTH_VALUE(SampleInput(DEPTH_BUFFER_INPUT_INDEX).x); }} +float SampleRawDepthLocation(float2 location) {{ return DEPTH_VALUE(SampleInputLocation(DEPTH_BUFFER_INPUT_INDEX, location).x); }} +float SampleDepth() {{ return ToLinearDepth(SampleRawDepth()); }} +float SampleDepthLocation(float2 location) {{ return ToLinearDepth(SampleRawDepthLocation(location)); }} +#define SampleOffset(offset) (SampleInputOffset(COLOR_BUFFER_INPUT_INDEX, offset)) +#define SampleLayerOffset(offset, layer) (SampleInputLayerOffset(COLOR_BUFFER_INPUT_INDEX, layer, offset)) +#define SamplePrevOffset(offset) (SampleInputOffset(PREV_PASS_OUTPUT_INPUT_INDEX, offset)) +#define SampleRawDepthOffset(offset) (DEPTH_VALUE(SampleInputOffset(DEPTH_BUFFER_INPUT_INDEX, offset).x)) +#define SampleDepthOffset(offset) (ToLinearDepth(SampleRawDepthOffset(offset))) + +float2 GetResolution() {{ return prev_resolution.xy; }} +float2 GetInvResolution() {{ return prev_resolution.zw; }} +float2 GetWindowResolution() {{ return window_resolution.xy; }} +float2 GetInvWindowResolution() {{ return window_resolution.zw; }} +float2 GetCoordinates() {{ return v_tex0.xy; }} + +float2 GetPrevResolution() {{ return prev_resolution.xy; }} +float2 GetInvPrevResolution() {{ return prev_resolution.zw; }} +float2 GetPrevRectOrigin() {{ return prev_rect.xy; }} +float2 GetPrevRectSize() {{ return prev_rect.zw; }} +float2 GetPrevCoordinates() {{ return v_tex0.xy; }} + +float2 GetSrcResolution() {{ return src_resolution.xy; }} +float2 GetInvSrcResolution() {{ return src_resolution.zw; }} +float2 GetSrcRectOrigin() {{ return src_rect.xy; }} +float2 GetSrcRectSize() {{ return src_rect.zw; }} +float2 GetSrcCoordinates() {{ return v_tex1.xy; }} + +float4 GetFragmentCoord() {{ return gl_FragCoord; }} + +float GetLayer() {{ return u_layer; }} +float GetTime() {{ return u_time; }} + +void SetOutput(float4 color) {{ ocol0 = color; }} + +#define GetOption(x) (x) +#define OptionEnabled(x) (x) + +)"); +} + +void VertexPixelShader::PixelShaderFooter(ShaderCode& shader_source, + const ShaderConfigPass& config_pass) const +{ + if (g_ActiveConfig.backend_info.api_type == APIType::D3D) + { + shader_source.Write("#undef main\n"); + shader_source.Write("void main(in float3 v_tex0_ : TEXCOORD0,\n"); + shader_source.Write("\tin float3 v_tex1_ : TEXCOORD1,\n"); + shader_source.Write("\tin float4 pos : SV_Position,\n"); + shader_source.Write("\tout float4 ocol0_ : SV_Target)\n"); + shader_source.Write("{{\n"); + shader_source.Write("\tv_tex0 = v_tex0_;\n"); + shader_source.Write("\tv_tex1 = v_tex1_;\n"); + shader_source.Write("\tv_fragcoord = pos;\n"); + + if (config_pass.m_entry_point == "main") + { + shader_source.Write("\treal_main();\n"); + } + else if (config_pass.m_entry_point.empty()) + { + // No entry point should create a copy + shader_source.Write("\tocol0 = SampleInput(0);\n"); + } + else + { + shader_source.Write("\t{}();\n", config_pass.m_entry_point); + } + shader_source.Write("\tocol0_ = ocol0;\n"); + shader_source.Write("}}\n"); + } + else + { + if (config_pass.m_entry_point.empty()) + { + // No entry point should create a copy + shader_source.Write("void main() {{ ocol0 = SampleInput(0);\n }}\n"); + } + else if (config_pass.m_entry_point != "main") + { + shader_source.Write("void main()\n"); + shader_source.Write("{{\n"); + shader_source.Write("\t{}();\n", config_pass.m_entry_point); + shader_source.Write("}}\n"); + } + } +} +void VertexPixelShader::VertexShaderMain(ShaderCode& shader_source) const +{ + if (g_ActiveConfig.backend_info.api_type == APIType::D3D) + { + shader_source.Write("void main(in uint id : SV_VertexID, out float3 v_tex0 : TEXCOORD0,\n"); + shader_source.Write( + " out float3 v_tex1 : TEXCOORD1, out float4 opos : SV_Position) {{\n"); + } + else + { + if (g_ActiveConfig.backend_info.bSupportsGeometryShaders) + { + shader_source.Write("VARYING_LOCATION(0) out VertexData {{\n"); + shader_source.Write(" float3 v_tex0;\n"); + shader_source.Write(" float3 v_tex1;\n"); + shader_source.Write("}};\n"); + } + else + { + shader_source.Write("VARYING_LOCATION(0) out float3 v_tex0;\n"); + shader_source.Write("VARYING_LOCATION(0) out float3 v_tex1;\n"); + } + + shader_source.Write("#define id gl_VertexID\n"); + shader_source.Write("#define opos gl_Position\n"); + shader_source.Write("void main() {{\n"); + } + shader_source.Write(" v_tex0 = float3(float((id << 1) & 2), float(id & 2), 0.0f);\n"); + shader_source.Write( + " opos = float4(v_tex0.xy * float2(2.0f, -2.0f) + float2(-1.0f, 1.0f), 0.0f, 1.0f);\n"); + shader_source.Write(" v_tex1 = float3(src_rect.xy + (src_rect.zw * v_tex0.xy), 0.0f);\n"); + + if (g_ActiveConfig.backend_info.api_type == APIType::Vulkan) + shader_source.Write(" opos.y = -opos.y;\n"); + + shader_source.Write("}}\n"); +} +} // namespace VideoCommon::PE diff --git a/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEShader.h b/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEShader.h new file mode 100644 index 000000000000..bb5860693f1d --- /dev/null +++ b/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEShader.h @@ -0,0 +1,50 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include "VideoCommon/PEShaderSystem/Runtime/PEBaseShader.h" + +#include +#include + +#include "Common/CommonTypes.h" +#include "VideoCommon/AbstractShader.h" +#include "VideoCommon/PEShaderSystem/Config/PEShaderConfig.h" +#include "VideoCommon/PEShaderSystem/Runtime/BuiltinUniforms.h" +#include "VideoCommon/PEShaderSystem/Runtime/PEShaderApplyOptions.h" +#include "VideoCommon/PEShaderSystem/Runtime/PEShaderPass.h" + +class AbstractTexture; +class ShaderCode; + +namespace VideoCommon::PE +{ +class ShaderOption; +class VertexPixelShader final : public BaseShader +{ +public: + bool CreatePasses(const ShaderConfig& config) override; + bool CreateAllPassOutputTextures(u32 width, u32 height, u32 layers, AbstractTextureFormat format) override; + bool RebuildPipeline(AbstractTextureFormat format, u32 layers) override; + void Apply(bool skip_final_copy, const ShaderApplyOptions& options, + const AbstractTexture* prev_pass_texture, float prev_pass_output_scale) override; + + bool SupportsDirectWrite() const override { return true; } + +private: + bool RecompilePasses(const ShaderConfig& config) override; + std::unique_ptr CompileGeometryShader(const ShaderConfig& config) const; + std::shared_ptr CompileVertexShader(const ShaderConfig& config) const; + std::unique_ptr CompilePixelShader(const ShaderConfig& config, + const ShaderPass& pass, + const ShaderConfigPass& config_pass) const; + void PixelShaderFooter(ShaderCode& shader_source, const ShaderConfigPass& config_pass) const; + void PixelShaderHeader(ShaderCode& shader_source, const ShaderPass& pass) const; + void VertexShaderMain(ShaderCode& shader_source) const; + AbstractTextureFormat m_last_pipeline_format; + u32 m_last_pipeline_layers; + std::unique_ptr m_geometry_shader; +}; +} // namespace VideoCommon::PE diff --git a/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEShaderApplyOptions.h b/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEShaderApplyOptions.h new file mode 100644 index 000000000000..bdb63fa715ae --- /dev/null +++ b/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEShaderApplyOptions.h @@ -0,0 +1,27 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include "Common/CommonTypes.h" +#include "Common/MathUtil.h" + +class AbstractFramebuffer; +class AbstractTexture; + +namespace VideoCommon::PE +{ + struct ShaderApplyOptions + { + AbstractFramebuffer* m_dest_fb; + MathUtil::Rectangle m_dest_rect; + const AbstractTexture* m_source_color_tex; + const AbstractTexture* m_source_depth_tex; + MathUtil::Rectangle m_source_rect; + int m_source_layer; + u64 m_time_elapsed; + float m_depth_near; + float m_depth_far; + }; +} diff --git a/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEShaderGroup.cpp b/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEShaderGroup.cpp new file mode 100644 index 000000000000..ba18de8fea35 --- /dev/null +++ b/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEShaderGroup.cpp @@ -0,0 +1,245 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "VideoCommon/PEShaderSystem/Runtime/PEShaderGroup.h" + +#include + +#include "Common/Logging/Log.h" +#include "VideoCommon/AbstractFramebuffer.h" +#include "VideoCommon/AbstractPipeline.h" +#include "VideoCommon/AbstractShader.h" +#include "VideoCommon/AbstractTexture.h" +#include "VideoCommon/PEShaderSystem/Config/PEShaderConfig.h" +#include "VideoCommon/PEShaderSystem/Runtime/PEComputeShader.h" +#include "VideoCommon/PEShaderSystem/Runtime/PEShader.h" +#include "VideoCommon/RenderBase.h" +#include "VideoCommon/VideoConfig.h" + +namespace VideoCommon::PE +{ +void ShaderGroup::Initialize() +{ + const auto config = ShaderConfig::CreateDefaultShader(); + s_default_shader = std::make_unique(); + s_default_shader->CreateOptions(config); + s_default_shader->CreatePasses(config); + s_default_shader->UpdateConfig(config); +} + +void ShaderGroup::Shutdown() +{ + s_default_shader.reset(); +} + +void ShaderGroup::UpdateConfig(const ShaderConfigGroup& group) +{ + const bool needs_compile = group.m_changes != m_last_change_count; + m_last_change_count = group.m_changes; + if (needs_compile) + { + g_renderer->WaitForGPUIdle(); + if (!CreateShaders(group)) + { + m_shaders.clear(); + } + } + else if (!m_shaders.empty()) + { + m_skip = true; + + bool failure = false; + std::size_t enabled_shader_index = 0; + for (std::size_t i = 0; i < group.m_shader_order.size(); i++) + { + const u32& shader_index = group.m_shader_order[i]; + const auto& config_shader = group.m_shaders[shader_index]; + if (!config_shader.m_enabled) + continue; + + m_skip = false; + + auto& shader = m_shaders[enabled_shader_index]; + if (!shader->UpdateConfig(config_shader)) + { + failure = true; + break; + } + + enabled_shader_index++; + } + + if (failure) + { + m_shaders.clear(); + } + } +} + +void ShaderGroup::Apply(const ShaderApplyOptions& options) +{ + const u32 dest_rect_width = static_cast(options.m_dest_rect.GetWidth()); + const u32 dest_rect_height = static_cast(options.m_dest_rect.GetHeight()); + + const auto update_default_shader = [&](const AbstractTexture* last_texture) { + if (m_default_target_width != dest_rect_width || m_default_target_height != dest_rect_height || + m_default_target_layers != options.m_dest_fb->GetLayers() || + m_default_target_format != options.m_dest_fb->GetColorFormat()) + { + const bool rebuild_pipelines = + m_default_target_format != options.m_dest_fb->GetColorFormat() || + m_default_target_layers != options.m_dest_fb->GetLayers(); + m_default_target_width = dest_rect_width; + m_default_target_height = dest_rect_height; + m_default_target_layers = options.m_dest_fb->GetLayers(); + m_default_target_format = options.m_dest_fb->GetColorFormat(); + + s_default_shader->CreateAllPassOutputTextures(dest_rect_width, dest_rect_height, + options.m_dest_fb->GetLayers(), + options.m_dest_fb->GetColorFormat()); + s_default_shader->UpdatePassInputs(last_texture); + if (rebuild_pipelines) + { + s_default_shader->RebuildPipeline(m_default_target_format, m_default_target_layers); + } + } + }; + if (m_shaders.empty() || m_skip) + { + const bool skip_final_copy = + options.m_dest_fb->GetColorAttachment() != options.m_source_color_tex && + options.m_dest_fb->GetSamples() == 1; + + update_default_shader(options.m_source_color_tex); + s_default_shader->Apply(skip_final_copy, options, options.m_source_color_tex, 1.0f); + return; + } + + if (m_target_width != dest_rect_width || m_target_height != dest_rect_height || + m_target_layers != options.m_dest_fb->GetLayers() || + m_target_format != options.m_dest_fb->GetColorFormat()) + { + const bool rebuild_pipelines = m_target_format != options.m_dest_fb->GetColorFormat() || + m_target_layers != options.m_dest_fb->GetLayers(); + m_target_width = dest_rect_width; + m_target_height = dest_rect_height; + m_target_layers = options.m_dest_fb->GetLayers(); + m_target_format = options.m_dest_fb->GetColorFormat(); + + bool failure = false; + const AbstractTexture* last_texture = options.m_source_color_tex; + for (auto& shader : m_shaders) + { + if (!shader->CreateAllPassOutputTextures(dest_rect_width, dest_rect_height, + options.m_dest_fb->GetLayers(), + options.m_dest_fb->GetColorFormat())) + { + failure = true; + break; + } + + shader->UpdatePassInputs(last_texture); + + if (rebuild_pipelines) + { + if (!shader->RebuildPipeline(m_target_format, m_target_layers)) + { + failure = true; + break; + } + } + const auto passes = shader->GetPasses(); + last_texture = passes.back()->m_output_texture.get(); + } + + if (failure) + { + // Clear shaders so we won't try again until rebuilt + m_shaders.clear(); + return; + } + } + + const AbstractTexture* last_texture = options.m_source_color_tex; + float output_scale = 1.0f; + for (std::size_t i = 0; i < m_shaders.size() - 1; i++) + { + auto& shader = m_shaders[i]; + const bool skip_final_copy = false; + shader->Apply(skip_final_copy, options, last_texture, output_scale); + + const auto passes = shader->GetPasses(); + const auto& last_pass = passes.back(); + last_texture = last_pass->m_output_texture.get(); + output_scale = last_pass->m_output_scale; + } + + const auto& last_shader = m_shaders.back(); + const auto passes = last_shader->GetPasses(); + const auto& last_pass = passes.back(); + const bool last_pass_scaled = last_pass->m_output_scale != 1.0; + const bool last_pass_uses_color_buffer = + std::any_of(last_pass->m_inputs.begin(), last_pass->m_inputs.end(), + [](auto&& input) { return input->GetType() == InputType::ColorBuffer; }); + + // Determine whether we can skip the final copy by writing directly to the output texture, if the + // last pass is not scaled, and the target isn't multisampled. + const bool skip_final_copy = + !last_pass_scaled && !last_pass_uses_color_buffer && + options.m_dest_fb->GetColorAttachment() != options.m_source_color_tex && + options.m_dest_fb->GetSamples() == 1 && last_shader->SupportsDirectWrite(); + + const auto last_index = m_shaders.size() - 1; + last_shader->Apply(skip_final_copy, options, last_texture, output_scale); + + if (!skip_final_copy) + { + update_default_shader(last_pass->m_output_texture.get()); + s_default_shader->Apply(true, options, last_pass->m_output_texture.get(), 1.0f); + } +} + +bool ShaderGroup::CreateShaders(const ShaderConfigGroup& group) +{ + m_target_width = 0; + m_target_height = 0; + m_target_format = AbstractTextureFormat::Undefined; + m_target_layers = 0; + + m_shaders.clear(); + for (const u32& shader_index : group.m_shader_order) + { + const auto& config_shader = group.m_shaders[shader_index]; + if (!config_shader.m_enabled) + continue; + + std::unique_ptr shader; + switch (config_shader.m_type) + { + case ShaderConfig::Type::VertexPixel: + shader = std::make_unique(); + break; + case ShaderConfig::Type::Compute: + { + if (!g_ActiveConfig.backend_info.bSupportsComputeShaders) + { + ERROR_LOG_FMT(VIDEO, + "Failed to create compute shader, backend doesn't support compute shaders!"); + return false; + } + shader = std::make_unique(); + break; + } + } + shader->CreateOptions(config_shader); + if (!shader->CreatePasses(config_shader)) + { + return false; + } + m_shaders.push_back(std::move(shader)); + } + + return true; +} +} // namespace VideoCommon::PE diff --git a/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEShaderGroup.h b/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEShaderGroup.h new file mode 100644 index 000000000000..a8063fe21fdd --- /dev/null +++ b/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEShaderGroup.h @@ -0,0 +1,44 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include + +#include "Common/CommonTypes.h" +#include "VideoCommon/PEShaderSystem/Config/PEShaderConfigGroup.h" +#include "VideoCommon/PEShaderSystem/Runtime/PEBaseShader.h" +#include "VideoCommon/PEShaderSystem/Runtime/PEShaderApplyOptions.h" +#include "VideoCommon/TextureConfig.h" + +namespace VideoCommon::PE +{ +class ShaderGroup +{ +public: + static void Initialize(); + static void Shutdown(); + void UpdateConfig(const ShaderConfigGroup& group); + void Apply(const ShaderApplyOptions& options); + +private: + bool CreateShaders(const ShaderConfigGroup& group); + std::optional m_last_change_count = 0; + std::vector> m_shaders; + bool m_skip = false; + + static inline std::unique_ptr s_default_shader; + u32 m_default_target_width; + u32 m_default_target_height; + u32 m_default_target_layers; + AbstractTextureFormat m_default_target_format; + + u32 m_target_width; + u32 m_target_height; + u32 m_target_layers; + AbstractTextureFormat m_target_format; +}; +} // namespace VideoCommon::PE diff --git a/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEShaderInput.cpp b/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEShaderInput.cpp new file mode 100644 index 000000000000..ed1f59a3f889 --- /dev/null +++ b/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEShaderInput.cpp @@ -0,0 +1,172 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "VideoCommon/PEShaderSystem/Runtime/PEShaderInput.h" + +#include + +#include "Common/CommonTypes.h" +#include "Common/IOFile.h" +#include "Common/Image.h" +#include "Common/VariantUtil.h" +#include "VideoCommon/AbstractTexture.h" +#include "VideoCommon/PEShaderSystem/Runtime/PEBaseShaderPass.h" +#include "VideoCommon/RenderBase.h" +#include "VideoCommon/TextureConfig.h" + +namespace VideoCommon::PE +{ +namespace +{ +class RuntimeImage final : public ShaderInput +{ +public: + static std::unique_ptr Create(std::string path, u32 texture_unit, + SamplerState state) + { + File::IOFile file(path, "rb"); + std::vector buffer(file.GetSize()); + if (!file.IsOpen() || !file.ReadBytes(buffer.data(), file.GetSize())) + return nullptr; + + struct ExternalImage + { + std::vector data; + u32 width; + u32 height; + }; + + ExternalImage image; + if (!Common::LoadPNG(buffer, &image.data, &image.width, &image.height)) + return nullptr; + + auto texture = g_renderer->CreateTexture( + TextureConfig(image.width, image.height, 1, 1, 1, AbstractTextureFormat::RGBA8, 0)); + if (!texture) + return nullptr; + + texture->Load(0, image.width, image.height, image.width, image.data.data(), + image.width * sizeof(u32) * image.height); + + return std::make_unique(std::move(texture), texture_unit, state); + } + RuntimeImage(std::unique_ptr texture, u32 texture_unit, SamplerState state) + : ShaderInput(InputType::ExternalImage, texture_unit, state), m_texture(std::move(texture)) + { + } + void Update(const AbstractTexture*, const std::vector&) override {} + + const AbstractTexture* GetTexture() const override { return m_texture.get(); } + +private: + std::unique_ptr m_texture; +}; + +class RuntimeGenericInput final : public ShaderInput +{ +public: + RuntimeGenericInput(InputType type, u32 texture_unit, SamplerState state) + : ShaderInput(type, texture_unit, state) + { + } + + void Update(const AbstractTexture*, const std::vector&) override {} + + const AbstractTexture* GetTexture() const override { return nullptr; } +}; + +class RuntimePreviousPass final : public ShaderInput +{ +public: + RuntimePreviousPass(u32 texture_unit, SamplerState state) + : ShaderInput(InputType::PreviousPassOutput, texture_unit, state) + { + } + + void Update(const AbstractTexture* previous_texture, const std::vector&) override + { + m_texture = previous_texture; + } + + const AbstractTexture* GetTexture() const override { return m_texture; } + +private: + const AbstractTexture* m_texture = nullptr; +}; + +class RuntimePass final : public ShaderInput +{ +public: + RuntimePass(u32 index, u32 texture_unit, SamplerState state) + : ShaderInput(InputType::PassOutput, texture_unit, state), m_index(index) + { + } + + void Update(const AbstractTexture*, const std::vector& passes) override + { + m_texture = passes[m_index]->m_output_texture.get(); + } + + const AbstractTexture* GetTexture() const override { return m_texture; } + +private: + u32 m_index; + const AbstractTexture* m_texture = nullptr; +}; +} // namespace +std::unique_ptr ShaderInput::Create(const ShaderConfigInput& input_config) +{ + std::unique_ptr result; + std::visit(overloaded{[&](UserImage i) { + result = + RuntimeImage::Create(i.m_path, i.m_texture_unit, i.m_sampler_state); + }, + [&](InternalImage i) { + result = + RuntimeImage::Create(i.m_path, i.m_texture_unit, i.m_sampler_state); + }, + [&](ColorBuffer i) { + result = std::make_unique( + InputType::ColorBuffer, i.m_texture_unit, i.m_sampler_state); + }, + [&](DepthBuffer i) { + result = std::make_unique( + InputType::DepthBuffer, i.m_texture_unit, i.m_sampler_state); + }, + [&](PreviousPass i) { + result = std::make_unique( + InputType::PreviousPassOutput, i.m_texture_unit, i.m_sampler_state); + }, + [&](PreviousShader i) { + result = std::make_unique( + InputType::PreviousShaderOutput, i.m_texture_unit, i.m_sampler_state); + }, + [&](ExplicitPass i) { + result = std::make_unique(i.m_pass_index, i.m_texture_unit, + i.m_sampler_state); + }}, + input_config); + return result; +} + +ShaderInput::ShaderInput(InputType type, u32 texture_unit, SamplerState state) + : m_type(type), m_texture_unit(texture_unit), m_sampler_state(state) +{ +} + +InputType ShaderInput::GetType() const +{ + return m_type; +} + +u32 ShaderInput::GetTextureUnit() const +{ + return m_texture_unit; +} + +SamplerState ShaderInput::GetSamplerState() const +{ + return m_sampler_state; +} +} // namespace VideoCommon::PE diff --git a/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEShaderInput.h b/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEShaderInput.h new file mode 100644 index 000000000000..1cc46f70be7f --- /dev/null +++ b/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEShaderInput.h @@ -0,0 +1,52 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include + +#include "Common/CommonTypes.h" +#include "VideoCommon/PEShaderSystem/Config/PEShaderConfigInput.h" +#include "VideoCommon/RenderState.h" + +class AbstractTexture; + +namespace VideoCommon::PE +{ +enum class InputType : u32 +{ + ExternalImage, // external image loaded from file + ColorBuffer, // colorbuffer at internal resolution + DepthBuffer, // depthbuffer at internal resolution + PreviousPassOutput, // output of the previous pass + PreviousShaderOutput, // output of the previous shader or the color buffer + PassOutput // output of a previous pass +}; + +struct BaseShaderPass; + +class ShaderInput +{ +public: + static std::unique_ptr Create(const ShaderConfigInput& input_config); + ShaderInput(InputType type, u32 texture_unit, SamplerState state); + virtual ~ShaderInput() = default; + ShaderInput(const ShaderInput&) = default; + ShaderInput(ShaderInput&&) = default; + ShaderInput& operator=(const ShaderInput&) = default; + ShaderInput& operator=(ShaderInput&&) = default; + virtual void Update(const AbstractTexture* previous_texture, + const std::vector& passes) = 0; + + InputType GetType() const; + u32 GetTextureUnit() const; + SamplerState GetSamplerState() const; + virtual const AbstractTexture* GetTexture() const = 0; + +private: + InputType m_type; + u32 m_texture_unit; + SamplerState m_sampler_state; +}; +} // namespace VideoCommon::PE diff --git a/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEShaderOption.cpp b/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEShaderOption.cpp new file mode 100644 index 000000000000..1594d8b4d9c8 --- /dev/null +++ b/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEShaderOption.cpp @@ -0,0 +1,103 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "VideoCommon/PEShaderSystem/Runtime/PEShaderOption.h" +#include "VideoCommon/PEShaderSystem/Runtime/RuntimeShaderImpl.h" + +#include +#include + +#include "Common/VariantUtil.h" +#include "VideoCommon/ShaderGenCommon.h" + +namespace VideoCommon::PE +{ +std::unique_ptr ShaderOption::Create(const ShaderConfigOption& config_option) +{ + std::unique_ptr result; + std::visit(overloaded{[&](EnumChoiceOption o) { + result = std::make_unique( + o.m_common.m_name, o.m_common.m_compile_time_constant, + RuntimeEnumOption::OptionType{o.m_values_for_choices[o.m_index]}); + }, + [&](FloatOption<1> o) { + result = std::make_unique>( + o.m_common.m_name, o.m_common.m_compile_time_constant, o.m_value); + }, + [&](FloatOption<2> o) { + result = std::make_unique>( + o.m_common.m_name, o.m_common.m_compile_time_constant, o.m_value); + }, + [&](FloatOption<3> o) { + result = std::make_unique>( + o.m_common.m_name, o.m_common.m_compile_time_constant, o.m_value); + }, + [&](FloatOption<4> o) { + result = std::make_unique>( + o.m_common.m_name, o.m_common.m_compile_time_constant, o.m_value); + }, + [&](IntOption<1> o) { + result = std::make_unique>( + o.m_common.m_name, o.m_common.m_compile_time_constant, o.m_value); + }, + [&](IntOption<2> o) { + result = std::make_unique>( + o.m_common.m_name, o.m_common.m_compile_time_constant, o.m_value); + }, + [&](IntOption<3> o) { + result = std::make_unique>( + o.m_common.m_name, o.m_common.m_compile_time_constant, o.m_value); + }, + [&](IntOption<4> o) { + result = std::make_unique>( + o.m_common.m_name, o.m_common.m_compile_time_constant, o.m_value); + }, + [&](BoolOption o) { + result = std::make_unique( + o.m_common.m_name, o.m_common.m_compile_time_constant, o.m_value); + }, + [&](ColorOption o) { + result = std::make_unique( + o.m_common.m_name, o.m_common.m_compile_time_constant, o.m_value); + }, + [&](ColorAlphaOption o) { + result = std::make_unique( + o.m_common.m_name, o.m_common.m_compile_time_constant, o.m_value); + }}, + config_option); + return result; +} + +std::size_t ShaderOption::Size() const +{ + if (m_evaluate_at_compile_time) + return 0; + + return SizeImpl(); +} + +void ShaderOption::WriteToMemory(u8*& buffer) const +{ + if (m_evaluate_at_compile_time) + return; + + return WriteToMemoryImpl(buffer); +} + +void ShaderOption::WriteShaderUniforms(ShaderCode& shader_source) const +{ + if (m_evaluate_at_compile_time) + return; + + WriteShaderUniformsImpl(shader_source); +} + +void ShaderOption::WriteShaderConstants(ShaderCode& shader_source) const +{ + if (!m_evaluate_at_compile_time) + return; + + WriteShaderConstantsImpl(shader_source); +} +} // namespace VideoCommon::PE diff --git a/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEShaderOption.h b/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEShaderOption.h new file mode 100644 index 000000000000..9249c9a92796 --- /dev/null +++ b/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEShaderOption.h @@ -0,0 +1,41 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include + +#include "Common/CommonTypes.h" +#include "VideoCommon/PEShaderSystem/Config/PEShaderConfigOption.h" + +class ShaderCode; + +namespace VideoCommon::PE +{ +class ShaderOption +{ +public: + static std::unique_ptr Create(const ShaderConfigOption& config_option); + ShaderOption() = default; + virtual ~ShaderOption() = default; + ShaderOption(const ShaderOption&) = default; + ShaderOption(ShaderOption&&) = default; + ShaderOption& operator=(const ShaderOption&) = default; + ShaderOption& operator=(ShaderOption&&) = default; + virtual void Update(const ShaderConfigOption& config_option) = 0; + void WriteToMemory(u8*& buffer) const; + void WriteShaderUniforms(ShaderCode& shader_source) const; + void WriteShaderConstants(ShaderCode& shader_source) const; + std::size_t Size() const; + +protected: + bool m_evaluate_at_compile_time = false; + +private: + virtual std::size_t SizeImpl() const = 0; + virtual void WriteToMemoryImpl(u8*& buffer) const = 0; + virtual void WriteShaderUniformsImpl(ShaderCode& shader_source) const = 0; + virtual void WriteShaderConstantsImpl(ShaderCode& shader_source) const = 0; +}; +} // namespace VideoCommon::PE diff --git a/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEShaderPass.h b/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEShaderPass.h new file mode 100644 index 000000000000..ff93627e6ccd --- /dev/null +++ b/Source/Core/VideoCommon/PEShaderSystem/Runtime/PEShaderPass.h @@ -0,0 +1,24 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include "VideoCommon/PEShaderSystem/Runtime/PEBaseShaderPass.h" + +#include + +#include "VideoCommon/AbstractFramebuffer.h" +#include "VideoCommon/AbstractPipeline.h" +#include "VideoCommon/AbstractShader.h" + +namespace VideoCommon::PE +{ +struct ShaderPass final : public BaseShaderPass +{ + std::shared_ptr m_vertex_shader; + std::unique_ptr m_pixel_shader; + std::unique_ptr m_pipeline; + std::unique_ptr m_output_framebuffer; +}; +} // namespace VideoCommon::PE diff --git a/Source/Core/VideoCommon/PEShaderSystem/Runtime/PETriggerPointManager.cpp b/Source/Core/VideoCommon/PEShaderSystem/Runtime/PETriggerPointManager.cpp new file mode 100644 index 000000000000..5fed1137a756 --- /dev/null +++ b/Source/Core/VideoCommon/PEShaderSystem/Runtime/PETriggerPointManager.cpp @@ -0,0 +1,286 @@ +#include "VideoCommon/PEShaderSystem/Runtime/PETriggerPointManager.h" + +#include + +#include "Common/VariantUtil.h" +#include "VideoCommon/FramebufferManager.h" +#include "VideoCommon/GraphicsTriggerManager.h" +#include "VideoCommon/RenderBase.h" +#include "VideoCommon/TextureInfo.h" +#include "VideoCommon/VideoConfig.h" + +namespace VideoCommon::PE +{ +void TriggerPointManager::UpdateConfig(const TriggerConfig& config, + const GraphicsTriggerManager& manager) +{ + const bool reset = config.m_changes != m_trigger_changes; + if (reset || m_post_group == nullptr) + { + m_trigger_name_to_group.clear(); + m_efb_shadergroups.clear(); + m_texture_shadergroups.clear(); + m_draw_call_2d_shadergroups.clear(); + m_draw_call_3d_shadergroups.clear(); + + m_post_group = &m_trigger_name_to_group[Constants::default_trigger]; + + m_trigger_changes = config.m_changes; + + for (const auto& [trigger_name, shader_group_config] : config.m_trigger_name_to_shader_groups) + { + auto& shader_group = m_trigger_name_to_group[trigger_name]; + const GraphicsTrigger* trigger = manager.GetTrigger(trigger_name); + if (!trigger) + continue; + + std::visit(overloaded{[&](const EFBGraphicsTrigger& t) { + m_efb_shadergroups.push_back({false, t, &shader_group}); + }, + [&](const TextureLoadGraphicsTrigger& t) { + m_texture_shadergroups.push_back({false, t, &shader_group}); + }, + [&](const DrawCall2DGraphicsTrigger& t) { + m_draw_call_2d_shadergroups.push_back({false, t, &shader_group}); + }, + [&](const DrawCall3DGraphicsTrigger& t) { + m_draw_call_3d_shadergroups.push_back({false, t, &shader_group}); + }, + [&](const PostGraphicsTrigger&) {}}, + *trigger); + } + } + + for (const auto& [trigger_name, shader_group_config] : config.m_trigger_name_to_shader_groups) + { + auto& shader_group = m_trigger_name_to_group[trigger_name]; + shader_group.UpdateConfig(shader_group_config); + } +} + +void TriggerPointManager::OnEFB(const MathUtil::Rectangle& srcRect, TextureFormat format) +{ + const u32 width = srcRect.GetWidth(); + const u32 height = srcRect.GetHeight(); + + VideoCommon::PE::TriggerParameters parameters; + const auto scaled_src_rect = g_renderer->ConvertEFBRectangle(srcRect); + const auto framebuffer_rect = g_renderer->ConvertFramebufferRectangle( + scaled_src_rect, g_framebuffer_manager->GetEFBFramebuffer()); + parameters.m_dest_fb = g_framebuffer_manager->GetEFBFramebuffer(); + parameters.m_dest_rect = g_framebuffer_manager->GetEFBFramebuffer()->GetRect(); + parameters.m_source_color_tex = g_framebuffer_manager->ResolveEFBColorTexture(framebuffer_rect); + parameters.m_source_depth_tex = g_framebuffer_manager->ResolveEFBDepthTexture(framebuffer_rect); + parameters.m_source_layer = 0; + parameters.m_source_rect = srcRect; + const auto options = OptionsFromParameters(parameters); + for (auto& [seen, trigger, shader_group] : m_efb_shadergroups) + { + if (seen) + { + continue; + } + + const bool format_matches = (trigger.format_operation == MultiGenericOperation::Exact && + !trigger.formats.empty() && trigger.formats[0] == format) || + (trigger.format_operation == MultiGenericOperation::OneOf && + std::any_of(trigger.formats.begin(), trigger.formats.end(), + [&](TextureFormat f) { return f == format; })) || + trigger.format_operation == MultiGenericOperation::Any; + const bool width_matches = + (trigger.width_operation == NumericOperation::Exact && trigger.width == width) || + (trigger.width_operation == NumericOperation::Greater && width > trigger.width) || + (trigger.width_operation == NumericOperation::Greater_Equal && width >= trigger.width) || + (trigger.width_operation == NumericOperation::Less && width < trigger.width) || + (trigger.width_operation == NumericOperation::Less_Equal && width <= trigger.width) || + trigger.width_operation == NumericOperation::Any; + const bool height_matches = + (trigger.height_operation == NumericOperation::Exact && trigger.height == height) || + (trigger.height_operation == NumericOperation::Greater && height > trigger.height) || + (trigger.height_operation == NumericOperation::Greater_Equal && height >= trigger.height) || + (trigger.height_operation == NumericOperation::Less && height < trigger.height) || + (trigger.height_operation == NumericOperation::Less_Equal && height <= trigger.height) || + trigger.height_operation == NumericOperation::Any; + + if (format_matches && width_matches && height_matches) + { + seen = true; + shader_group->Apply(options); + } + } +} + +void TriggerPointManager::OnTextureLoad(const TriggerParameters&) +{ +} + +void TriggerPointManager::OnDraw(ProjectionType type, const std::vector& textures) +{ + int scissor_x_off = bpmem.scissorOffset.x * 2; + int scissor_y_off = bpmem.scissorOffset.y * 2; + float x = g_renderer->EFBToScaledXf(xfmem.viewport.xOrig - xfmem.viewport.wd - scissor_x_off); + float y = g_renderer->EFBToScaledYf(xfmem.viewport.yOrig + xfmem.viewport.ht - scissor_y_off); + + float width = g_renderer->EFBToScaledXf(2.0f * xfmem.viewport.wd); + float height = g_renderer->EFBToScaledYf(-2.0f * xfmem.viewport.ht); + if (width < 0.f) + { + x += width; + width *= -1; + } + if (height < 0.f) + { + y += height; + height *= -1; + } + + // Clamp to size if oversized not supported. Required for D3D. + if (!g_ActiveConfig.backend_info.bSupportsOversizedViewports) + { + const float max_width = static_cast(g_renderer->GetCurrentFramebuffer()->GetWidth()); + const float max_height = static_cast(g_renderer->GetCurrentFramebuffer()->GetHeight()); + x = std::clamp(x, 0.0f, max_width - 1.0f); + y = std::clamp(y, 0.0f, max_height - 1.0f); + width = std::clamp(width, 1.0f, max_width - x); + height = std::clamp(height, 1.0f, max_height - y); + } + + // Lower-left flip. + if (g_ActiveConfig.backend_info.bUsesLowerLeftOrigin) + y = static_cast(g_renderer->GetCurrentFramebuffer()->GetHeight()) - y - height; + + MathUtil::Rectangle srcRect; + srcRect.left = static_cast(x); + srcRect.top = static_cast(y); + srcRect.right = srcRect.left + static_cast(width); + srcRect.bottom = srcRect.top + static_cast(height); + + VideoCommon::PE::TriggerParameters parameters; + parameters.m_dest_fb = g_framebuffer_manager->GetEFBFramebuffer(); + parameters.m_dest_rect = g_framebuffer_manager->GetEFBFramebuffer()->GetRect(); + parameters.m_source_color_tex = g_framebuffer_manager->GetEFBColorTexture(); + parameters.m_source_depth_tex = g_framebuffer_manager->GetEFBDepthTexture(); + parameters.m_source_layer = 0; + parameters.m_source_rect = srcRect; + const auto options = OptionsFromParameters(parameters); + if (type == ProjectionType::Orthographic) + { + for (auto& [seen, trigger, shader_group] : m_draw_call_2d_shadergroups) + { + if (seen) + { + continue; + } + + for (const auto& texture : textures) + { + const bool format_matches = + (trigger.format_operation == MultiGenericOperation::Exact && !trigger.formats.empty() && + trigger.formats[0] == texture.GetTextureFormat()) || + (trigger.format_operation == MultiGenericOperation::OneOf && + std::any_of(trigger.formats.begin(), trigger.formats.end(), + [&](TextureFormat f) { return f == texture.GetTextureFormat(); })) || + trigger.format_operation == MultiGenericOperation::Any; + const bool width_matches = (trigger.width_operation == NumericOperation::Exact && + trigger.width == texture.GetRawWidth()) || + (trigger.width_operation == NumericOperation::Greater && + texture.GetRawWidth() > trigger.width) || + (trigger.width_operation == NumericOperation::Greater_Equal && + texture.GetRawWidth() >= trigger.width) || + (trigger.width_operation == NumericOperation::Less && + texture.GetRawWidth() < trigger.width) || + (trigger.width_operation == NumericOperation::Less_Equal && + texture.GetRawWidth() <= trigger.width) || + trigger.width_operation == NumericOperation::Any; + const bool height_matches = (trigger.height_operation == NumericOperation::Exact && + trigger.height == texture.GetRawHeight()) || + (trigger.height_operation == NumericOperation::Greater && + texture.GetRawHeight() > trigger.height) || + (trigger.height_operation == NumericOperation::Greater_Equal && + texture.GetRawHeight() >= trigger.height) || + (trigger.height_operation == NumericOperation::Less && + texture.GetRawHeight() < trigger.height) || + (trigger.height_operation == NumericOperation::Less_Equal && + texture.GetRawHeight() <= trigger.height) || + trigger.height_operation == NumericOperation::Any; + + if (format_matches && width_matches && height_matches) + { + seen = true; + shader_group->Apply(options); + break; + } + } + } + } + else + { + for (auto& [seen, trigger, shader_group] : m_draw_call_3d_shadergroups) + { + if (seen) + { + continue; + } + } + } +} + +void TriggerPointManager::OnPost(const TriggerParameters& trigger_parameters) +{ + const auto options = OptionsFromParameters(trigger_parameters); + m_post_group->Apply(options); +} + +void TriggerPointManager::SetDepthNearFar(float depth_near, float depth_far) +{ + m_depth_near = depth_near; + m_depth_far = depth_far; +} + +void TriggerPointManager::Start() +{ + m_timer.Start(); + ShaderGroup::Initialize(); +} + +void TriggerPointManager::Stop() +{ + m_timer.Stop(); + ShaderGroup::Shutdown(); +} + +void TriggerPointManager::ResetFrame() +{ + for (auto& [seen, trigger, shader_group] : m_efb_shadergroups) + { + seen = false; + } + + for (auto& [seen, trigger, shader_group] : m_draw_call_2d_shadergroups) + { + seen = false; + } + + for (auto& [seen, trigger, shader_group] : m_draw_call_3d_shadergroups) + { + seen = false; + } +} + +ShaderApplyOptions +TriggerPointManager::OptionsFromParameters(const TriggerParameters& trigger_parameters) +{ + ShaderApplyOptions options; + options.m_dest_fb = trigger_parameters.m_dest_fb; + options.m_dest_rect = trigger_parameters.m_dest_rect; + options.m_source_color_tex = trigger_parameters.m_source_color_tex; + options.m_source_depth_tex = trigger_parameters.m_source_depth_tex; + + options.m_source_rect = trigger_parameters.m_source_rect; + options.m_source_layer = trigger_parameters.m_source_layer; + options.m_time_elapsed = m_timer.GetTimeElapsed(); + options.m_depth_near = m_depth_near; + options.m_depth_far = m_depth_far; + return options; +} +} // namespace VideoCommon::PE diff --git a/Source/Core/VideoCommon/PEShaderSystem/Runtime/PETriggerPointManager.h b/Source/Core/VideoCommon/PEShaderSystem/Runtime/PETriggerPointManager.h new file mode 100644 index 000000000000..2cffc4e9a5f0 --- /dev/null +++ b/Source/Core/VideoCommon/PEShaderSystem/Runtime/PETriggerPointManager.h @@ -0,0 +1,66 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include + +#include "Common/CommonTypes.h" +#include "Common/MathUtil.h" +#include "Common/Timer.h" + +#include "VideoCommon/GraphicsTrigger.h" +#include "VideoCommon/PEShaderSystem/Config/PEShaderConfigGroup.h" +#include "VideoCommon/PEShaderSystem/Config/PETriggerConfig.h" +#include "VideoCommon/PEShaderSystem/Runtime/PEShaderGroup.h" +#include "VideoCommon/XFMemory.h" + +class AbstractFramebuffer; +class AbstractTexture; +class GraphicsTriggerManager; +class TextureInfo; + +namespace VideoCommon::PE +{ +struct TriggerParameters +{ + AbstractFramebuffer* m_dest_fb; + MathUtil::Rectangle m_dest_rect; + const AbstractTexture* m_source_color_tex; + const AbstractTexture* m_source_depth_tex; + MathUtil::Rectangle m_source_rect; + int m_source_layer; +}; + +class TriggerPointManager +{ +public: + void UpdateConfig(const TriggerConfig& config, const GraphicsTriggerManager& manager); + void OnEFB(const MathUtil::Rectangle& srcRect, TextureFormat format); + void OnTextureLoad(const TriggerParameters& trigger_parameters); + void OnDraw(ProjectionType type, const std::vector& textures); + void OnPost(const TriggerParameters& trigger_parameters); + void SetDepthNearFar(float depth_near, float depth_far); + void Start(); + void Stop(); + void ResetFrame(); + +private: + ShaderApplyOptions OptionsFromParameters(const TriggerParameters& trigger_parameters); + Common::Timer m_timer; + float m_depth_near = 0.0f; + float m_depth_far = 0.0f; + u64 m_trigger_changes = 0; + std::map m_trigger_name_to_group; + std::vector> m_efb_shadergroups; + std::vector> m_texture_shadergroups; + std::vector> + m_draw_call_2d_shadergroups; + std::vector> + m_draw_call_3d_shadergroups; + ShaderGroup* m_post_group = nullptr; +}; +} // namespace VideoCommon::PE diff --git a/Source/Core/VideoCommon/PEShaderSystem/Runtime/RuntimeShaderImpl.h b/Source/Core/VideoCommon/PEShaderSystem/Runtime/RuntimeShaderImpl.h new file mode 100644 index 000000000000..b2f79328dd66 --- /dev/null +++ b/Source/Core/VideoCommon/PEShaderSystem/Runtime/RuntimeShaderImpl.h @@ -0,0 +1,220 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +#include + +#include "Common/CommonTypes.h" +#include "VideoCommon/PEShaderSystem/Runtime/PEShaderOption.h" +#include "VideoCommon/ShaderGenCommon.h" + +namespace VideoCommon::PE +{ +template > +class RuntimeFloatOption final : public ShaderOption +{ +public: + using OptionType = std::array; + + explicit RuntimeFloatOption(std::string name) : m_name(std::move(name)) {} + RuntimeFloatOption(std::string name, bool compile_time_constant, OptionType value) + : m_name(std::move(name)), m_value(std::move(value)) + { + m_evaluate_at_compile_time = compile_time_constant; + } + + void Update(const OptionType& value) { m_value = value; } + + void Update(const ShaderConfigOption& config_option) override + { + if (auto v = std::get_if(&config_option)) + m_value = v->m_value; + } + + void WriteToMemoryImpl(u8*& buffer) const override + { + std::memcpy(buffer, m_value.data(), m_bytes); + std::memset(buffer + m_bytes, 0, m_padding_bytes); + buffer += SizeImpl(); + } + + void WriteShaderUniformsImpl(ShaderCode& shader_source) const override + { + if constexpr (N == 1UL) + { + shader_source.Write("float {};\n", m_name); + } + else + { + shader_source.Write("float{} {};\n", N, m_name); + } + for (int i = 0; i < m_padding; i++) + { + shader_source.Write("float _{}{}_padding;\n", m_name, i); + } + } + + void WriteShaderConstantsImpl(ShaderCode& shader_source) const override + { + if constexpr (N == 1UL) + { + shader_source.Write("#define {} float({})\n", m_name, m_value[0]); + } + else + { + shader_source.Write("#define {} float{}({})\n", m_name, N, fmt::join(m_value, ",")); + } + } + + std::size_t SizeImpl() const override { return m_bytes + m_padding_bytes; } + +private: + static_assert(N <= 4UL); + + static constexpr std::size_t m_bytes = N * sizeof(float); + static constexpr std::size_t m_padding = 4UL - N; + static constexpr std::size_t m_padding_bytes = m_padding * sizeof(float); + + OptionType m_value; + std::string m_name; +}; + +using RuntimeColorOption = RuntimeFloatOption<3, ColorOption>; +using RuntimeColorAlphaOption = RuntimeFloatOption<4, ColorAlphaOption>; + +template > +class RuntimeIntOption final : public ShaderOption +{ +public: + using OptionType = std::array; + + explicit RuntimeIntOption(std::string name) : m_name(std::move(name)) {} + RuntimeIntOption(std::string name, bool compile_time_constant, OptionType value) + : m_name(std::move(name)), m_value(std::move(value)) + { + m_evaluate_at_compile_time = compile_time_constant; + } + + void Update(const OptionType& value) { m_value = value; } + + void Update(const ShaderConfigOption& config_option) override + { + if (auto v = std::get_if(&config_option)) + { + if constexpr (std::is_same_v) + { + m_value[0] = v->m_values_for_choices[v->m_index]; + } + else + { + m_value = v->m_value; + } + } + } + + void WriteToMemoryImpl(u8*& buffer) const override + { + std::memcpy(buffer, m_value.data(), m_bytes); + std::memset(buffer + m_bytes, 0, m_padding_bytes); + buffer += SizeImpl(); + } + + void WriteShaderUniformsImpl(ShaderCode& shader_source) const override + { + if constexpr (N == 1UL) + { + shader_source.Write("int {};\n", m_name); + } + else + { + shader_source.Write("int{} {};\n", N, m_name); + } + for (int i = 0; i < m_padding; i++) + { + shader_source.Write("int _{}{}_padding;\n", m_name, i); + } + } + + void WriteShaderConstantsImpl(ShaderCode& shader_source) const override + { + if constexpr (N == 1UL) + { + shader_source.Write("#define {} int({})\n", m_name, m_value[0]); + } + else + { + shader_source.Write("#define {} int{}({})\n", m_name, N, fmt::join(m_value, ",")); + } + } + + std::size_t SizeImpl() const override { return m_bytes + m_padding_bytes; } + +private: + static_assert(N <= 4UL); + + static constexpr std::size_t m_bytes = N * sizeof(s32); + static constexpr std::size_t m_padding = 4UL - N; + static constexpr std::size_t m_padding_bytes = m_padding * sizeof(s32); + + OptionType m_value; + std::string m_name; +}; + +using RuntimeEnumOption = RuntimeIntOption<1, EnumChoiceOption>; + +class RuntimeBoolOption final : public ShaderOption +{ +public: + explicit RuntimeBoolOption(std::string name) : m_name(std::move(name)) {} + RuntimeBoolOption(std::string name, bool compile_time_constant, bool value) + : m_name(std::move(name)), m_value(value) + { + m_evaluate_at_compile_time = compile_time_constant; + } + + void Update(bool value) { m_value = value; } + + void Update(const ShaderConfigOption& config_option) override + { + if (auto v = std::get_if(&config_option)) + m_value = v->m_value; + } + + void WriteToMemoryImpl(u8*& buffer) const override + { + int val = m_value ? 1 : 0; + std::memcpy(buffer, &val, m_bytes); + std::memset(buffer + m_bytes, 0, m_padding_bytes); + buffer += SizeImpl(); + } + + void WriteShaderUniformsImpl(ShaderCode& shader_source) const override + { + shader_source.Write("bool {};\n", m_name); + for (int i = 0; i < m_padding; i++) + { + shader_source.Write("bool _{}{}_padding;\n", m_name, i); + } + } + + void WriteShaderConstantsImpl(ShaderCode& shader_source) const override + { + shader_source.Write("#define {} ({})\n", m_name, static_cast(m_value)); + } + + std::size_t SizeImpl() const override { return m_bytes + m_padding_bytes; } + +private: + static constexpr std::size_t m_bytes = 4; + static constexpr std::size_t m_padding = 3; + static constexpr std::size_t m_padding_bytes = m_padding * sizeof(u32); + + bool m_value; + std::string m_name; +}; +} // namespace VideoCommon::PE diff --git a/Source/Core/VideoCommon/RenderBase.cpp b/Source/Core/VideoCommon/RenderBase.cpp index 07fa239bba5e..7206fe4b5b18 100644 --- a/Source/Core/VideoCommon/RenderBase.cpp +++ b/Source/Core/VideoCommon/RenderBase.cpp @@ -70,7 +70,6 @@ #include "VideoCommon/OpcodeDecoding.h" #include "VideoCommon/PixelEngine.h" #include "VideoCommon/PixelShaderManager.h" -#include "VideoCommon/PostProcessing.h" #include "VideoCommon/ShaderCache.h" #include "VideoCommon/ShaderGenCommon.h" #include "VideoCommon/Statistics.h" @@ -120,15 +119,18 @@ bool Renderer::Initialize() if (!InitializeImGui()) return false; - m_post_processor = std::make_unique(); - if (!m_post_processor->Initialize(m_backbuffer_format)) - return false; + m_graphics_trigger_manager.Load(); + m_custom_shader_trigger_manager.Start(); + m_custom_shader_trigger_manager.UpdateConfig(g_ActiveConfig.m_trigger_config, + m_graphics_trigger_manager); return true; } void Renderer::Shutdown() { + m_custom_shader_trigger_manager.Stop(); + // Disable ControllerInterface's aspect ratio adjustments so mapping dialog behaves normally. g_controller_interface.SetAspectRatioAdjustment(1); @@ -136,7 +138,6 @@ void Renderer::Shutdown() // can require additional graphics sub-systems so it needs to be done first ShutdownFrameDumping(); ShutdownImGui(); - m_post_processor.reset(); } void Renderer::BeginUtilityDrawing() @@ -308,6 +309,8 @@ void Renderer::RenderToXFB(u32 xfbAddr, const MathUtil::Rectangle& sourceRc { CheckFifoRecording(); + m_custom_shader_trigger_manager.ResetFrame(); + if (!fbStride || !fbHeight) return; } @@ -445,15 +448,8 @@ void Renderer::CheckForConfigChanges() if (old_efb_access_tile_size != g_ActiveConfig.iEFBAccessTileSize) g_framebuffer_manager->SetEFBCacheTileSize(std::max(g_ActiveConfig.iEFBAccessTileSize, 0)); - // Check for post-processing shader changes. Done up here as it doesn't affect anything outside - // the post-processor. Note that options are applied every frame, so no need to check those. - if (m_post_processor->GetConfig()->GetShader() != g_ActiveConfig.sPostProcessingShader) - { - // The existing shader must not be in use when it's destroyed - WaitForGPUIdle(); - - m_post_processor->RecompileShader(); - } + m_custom_shader_trigger_manager.UpdateConfig(g_ActiveConfig.m_trigger_config, + m_graphics_trigger_manager); // Determine which (if any) settings have changed. ShaderHostConfig new_host_config = ShaderHostConfig::GetCurrent(); @@ -518,7 +514,6 @@ void Renderer::CheckForConfigChanges() if (changed_bits & CONFIG_CHANGE_BIT_STEREO_MODE) { RecompileImGuiPipeline(); - m_post_processor->RecompilePipeline(); } } @@ -1181,6 +1176,16 @@ void Renderer::ForceReloadTextures() m_force_reload_textures.Set(); } +const VideoCommon::PE::TriggerPointManager& Renderer::GetCustomShaderTriggerManager() const +{ + return m_custom_shader_trigger_manager; +} + +VideoCommon::PE::TriggerPointManager& Renderer::GetCustomShaderTriggerManager() +{ + return m_custom_shader_trigger_manager; +} + // Heuristic to detect if a GameCube game is in 16:9 anamorphic widescreen mode. void Renderer::UpdateWidescreenHeuristic() { @@ -1402,12 +1407,15 @@ void Renderer::RenderXFBToScreen(const MathUtil::Rectangle& target_rc, { const auto [left_rc, right_rc] = ConvertStereoRectangle(target_rc); - m_post_processor->BlitFromTexture(left_rc, source_rc, source_texture, 0); - m_post_processor->BlitFromTexture(right_rc, source_rc, source_texture, 1); + m_custom_shader_trigger_manager.OnPost(VideoCommon::PE::TriggerParameters{ + m_current_framebuffer, left_rc, source_texture, nullptr, source_rc, 0}); + m_custom_shader_trigger_manager.OnPost(VideoCommon::PE::TriggerParameters{ + m_current_framebuffer, left_rc, source_texture, nullptr, source_rc, 1}); } else { - m_post_processor->BlitFromTexture(target_rc, source_rc, source_texture, 0); + m_custom_shader_trigger_manager.OnPost(VideoCommon::PE::TriggerParameters{ + m_current_framebuffer, target_rc, source_texture, nullptr, source_rc, 0}); } } diff --git a/Source/Core/VideoCommon/RenderBase.h b/Source/Core/VideoCommon/RenderBase.h index ddc86cd3b3c3..c2a42ef9158c 100644 --- a/Source/Core/VideoCommon/RenderBase.h +++ b/Source/Core/VideoCommon/RenderBase.h @@ -30,6 +30,8 @@ #include "VideoCommon/BPMemory.h" #include "VideoCommon/FPSCounter.h" #include "VideoCommon/FrameDump.h" +#include "VideoCommon/GraphicsTriggerManager.h" +#include "VideoCommon/PEShaderSystem/Runtime/PETriggerPointManager.h" #include "VideoCommon/RenderState.h" #include "VideoCommon/TextureConfig.h" @@ -51,11 +53,6 @@ enum class EFBReinterpretType; enum class StagingTextureType; enum class AspectMode; -namespace VideoCommon -{ -class PostProcessing; -} // namespace VideoCommon - struct EfbPokeData { u16 x, y; @@ -237,7 +234,6 @@ class Renderer PixelFormat GetPrevPixelFormat() const { return m_prev_efb_format; } void StorePixelFormat(PixelFormat new_format) { m_prev_efb_format = new_format; } bool EFBHasAlphaChannel() const; - VideoCommon::PostProcessing* GetPostProcessor() const { return m_post_processor.get(); } // Final surface changing // This is called when the surface is resized (WX) or the window changes (Android). void ChangeSurface(void* new_surface_handle); @@ -263,6 +259,9 @@ class Renderer // Will forcibly reload all textures on the next swap void ForceReloadTextures(); + + const VideoCommon::PE::TriggerPointManager& GetCustomShaderTriggerManager() const; + VideoCommon::PE::TriggerPointManager& GetCustomShaderTriggerManager(); protected: // Bitmask containing information about which configuration has changed for the backend. @@ -332,8 +331,6 @@ class Renderer FPSCounter m_fps_counter; - std::unique_ptr m_post_processor; - void* m_new_surface_handle = nullptr; Common::Flag m_surface_changed; Common::Flag m_surface_resized; @@ -443,6 +440,9 @@ class Renderer std::unique_ptr m_netplay_chat_ui; Common::Flag m_force_reload_textures; + + VideoCommon::PE::TriggerPointManager m_custom_shader_trigger_manager; + GraphicsTriggerManager m_graphics_trigger_manager; }; extern std::unique_ptr g_renderer; diff --git a/Source/Core/VideoCommon/TextureCacheBase.cpp b/Source/Core/VideoCommon/TextureCacheBase.cpp index 766c0179158e..bdb5c82e43c3 100644 --- a/Source/Core/VideoCommon/TextureCacheBase.cpp +++ b/Source/Core/VideoCommon/TextureCacheBase.cpp @@ -1188,33 +1188,32 @@ class ArbitraryMipmapDetector std::vector levels; }; -TextureCacheBase::TCacheEntry* TextureCacheBase::Load(const u32 stage) +TextureCacheBase::TCacheEntry* TextureCacheBase::Load(const TextureInfo& texture_info) { // if this stage was not invalidated by changes to texture registers, keep the current texture - if (IsValidBindPoint(stage) && bound_textures[stage]) + if (IsValidBindPoint(texture_info.GetStage()) && bound_textures[texture_info.GetStage()]) { - return bound_textures[stage]; + return bound_textures[texture_info.GetStage()]; } - TextureInfo texture_info = TextureInfo::FromStage(stage); - auto entry = GetTexture(g_ActiveConfig.iSafeTextureCache_ColorSamples, texture_info); if (!entry) return nullptr; entry->frameCount = FRAMECOUNT_INVALID; - bound_textures[stage] = entry; + bound_textures[texture_info.GetStage()] = entry; // We need to keep track of invalided textures until they have actually been replaced or // re-loaded - valid_bind_points.set(stage); + valid_bind_points.set(texture_info.GetStage()); return entry; } TextureCacheBase::TCacheEntry* -TextureCacheBase::GetTexture(const int textureCacheSafetyColorSampleSize, TextureInfo& texture_info) +TextureCacheBase::GetTexture(const int textureCacheSafetyColorSampleSize, + const TextureInfo& texture_info) { u32 expanded_width = texture_info.GetExpandedWidth(); u32 expanded_height = texture_info.GetExpandedHeight(); @@ -2115,6 +2114,11 @@ void TextureCacheBase::CopyRenderTargetToTexture( copy_to_vram = false; } + if (!is_xfb_copy) + { + g_renderer->GetCustomShaderTriggerManager().OnEFB(srcRect, baseFormat); + } + // We also linear filtering for both box filtering and downsampling higher resolutions to 1x. // TODO: This only produces perfect downsampling for 2x IR, other resolutions will need more // complex down filtering to average all pixels and produce the correct result. diff --git a/Source/Core/VideoCommon/TextureCacheBase.h b/Source/Core/VideoCommon/TextureCacheBase.h index 0d456bfddff1..924f20f886d5 100644 --- a/Source/Core/VideoCommon/TextureCacheBase.h +++ b/Source/Core/VideoCommon/TextureCacheBase.h @@ -213,10 +213,11 @@ class TextureCacheBase void Invalidate(); - TCacheEntry* Load(const u32 stage); + TCacheEntry* Load(const TextureInfo& texture_info); static void InvalidateAllBindPoints() { valid_bind_points.reset(); } static bool IsValidBindPoint(u32 i) { return valid_bind_points.test(i); } - TCacheEntry* GetTexture(const int textureCacheSafetyColorSampleSize, TextureInfo& texture_info); + TCacheEntry* GetTexture(const int textureCacheSafetyColorSampleSize, + const TextureInfo& texture_info); TCacheEntry* GetXFBTexture(u32 address, u32 width, u32 height, u32 stride, MathUtil::Rectangle* display_rect); diff --git a/Source/Core/VideoCommon/TextureInfo.cpp b/Source/Core/VideoCommon/TextureInfo.cpp index 631e91c6eed8..f338f50bf30a 100644 --- a/Source/Core/VideoCommon/TextureInfo.cpp +++ b/Source/Core/VideoCommon/TextureInfo.cpp @@ -41,20 +41,20 @@ TextureInfo TextureInfo::FromStage(u32 stage) if (from_tmem) { - return TextureInfo(&texMem[tmem_address_even], tlut_ptr, address, texture_format, tlut_format, - width, height, true, &texMem[tmem_address_odd], &texMem[tmem_address_even], - mip_count); + return TextureInfo(stage, &texMem[tmem_address_even], tlut_ptr, address, texture_format, + tlut_format, width, height, true, &texMem[tmem_address_odd], + &texMem[tmem_address_even], mip_count); } - return TextureInfo(Memory::GetPointer(address), tlut_ptr, address, texture_format, tlut_format, - width, height, false, nullptr, nullptr, mip_count); + return TextureInfo(stage, Memory::GetPointer(address), tlut_ptr, address, texture_format, + tlut_format, width, height, false, nullptr, nullptr, mip_count); } -TextureInfo::TextureInfo(const u8* ptr, const u8* tlut_ptr, u32 address, +TextureInfo::TextureInfo(u32 stage, const u8* ptr, const u8* tlut_ptr, u32 address, TextureFormat texture_format, TLUTFormat tlut_format, u32 width, u32 height, bool from_tmem, const u8* tmem_odd, const u8* tmem_even, std::optional mip_count) - : m_ptr(ptr), m_tlut_ptr(tlut_ptr), m_address(address), m_from_tmem(from_tmem), + : m_stage(stage), m_ptr(ptr), m_tlut_ptr(tlut_ptr), m_address(address), m_from_tmem(from_tmem), m_tmem_odd(tmem_odd), m_texture_format(texture_format), m_tlut_format(tlut_format), m_raw_width(width), m_raw_height(height) { @@ -102,7 +102,7 @@ std::string TextureInfo::NameDetails::GetFullName() const return fmt::format("{}_{}{}_{}", base_name, texture_name, tlut_name, format_name); } -TextureInfo::NameDetails TextureInfo::CalculateTextureName() +TextureInfo::NameDetails TextureInfo::CalculateTextureName() const { if (!m_ptr) return NameDetails{}; @@ -242,6 +242,11 @@ u32 TextureInfo::GetRawHeight() const return m_raw_height; } +u32 TextureInfo::GetStage() const +{ + return m_stage; +} + bool TextureInfo::HasMipMaps() const { return !m_mip_levels.empty(); diff --git a/Source/Core/VideoCommon/TextureInfo.h b/Source/Core/VideoCommon/TextureInfo.h index 109715ba45b4..940b09b3cb19 100644 --- a/Source/Core/VideoCommon/TextureInfo.h +++ b/Source/Core/VideoCommon/TextureInfo.h @@ -17,9 +17,10 @@ class TextureInfo { public: static TextureInfo FromStage(u32 stage); - TextureInfo(const u8* ptr, const u8* tlut_ptr, u32 address, TextureFormat texture_format, - TLUTFormat tlut_format, u32 width, u32 height, bool from_tmem, const u8* tmem_odd, - const u8* tmem_even, std::optional mip_count); + TextureInfo(u32 stage, const u8* ptr, const u8* tlut_ptr, u32 address, + TextureFormat texture_format, TLUTFormat tlut_format, u32 width, u32 height, + bool from_tmem, const u8* tmem_odd, const u8* tmem_even, + std::optional mip_count); struct NameDetails { @@ -30,7 +31,7 @@ class TextureInfo std::string GetFullName() const; }; - NameDetails CalculateTextureName(); + NameDetails CalculateTextureName() const; const u8* GetData() const; const u8* GetTlutAddress() const; @@ -55,6 +56,8 @@ class TextureInfo u32 GetRawWidth() const; u32 GetRawHeight() const; + u32 GetStage() const; + class MipLevel { public: @@ -115,4 +118,6 @@ class TextureInfo u32 m_block_height; u32 m_expanded_height; u32 m_raw_height; + + u32 m_stage; }; diff --git a/Source/Core/VideoCommon/VertexManagerBase.cpp b/Source/Core/VideoCommon/VertexManagerBase.cpp index f78ec9fca273..be28397910f3 100644 --- a/Source/Core/VideoCommon/VertexManagerBase.cpp +++ b/Source/Core/VideoCommon/VertexManagerBase.cpp @@ -7,7 +7,6 @@ #include #include -#include "Common/BitSet.h" #include "Common/ChunkFile.h" #include "Common/CommonTypes.h" #include "Common/Logging/Log.h" @@ -30,6 +29,7 @@ #include "VideoCommon/SamplerCommon.h" #include "VideoCommon/Statistics.h" #include "VideoCommon/TextureCacheBase.h" +#include "VideoCommon/TextureInfo.h" #include "VideoCommon/VertexLoaderManager.h" #include "VideoCommon/VertexShaderManager.h" #include "VideoCommon/VideoBackendBase.h" @@ -335,7 +335,7 @@ bool VertexManagerBase::UploadTexelBuffer(const void* data, u32 data_size, Texel return false; } -void VertexManagerBase::LoadTextures() +BitSet32 VertexManagerBase::UsedTextures() { BitSet32 usedtextures; for (u32 i = 0; i < bpmem.genMode.numtevstages + 1u; ++i) @@ -347,8 +347,13 @@ void VertexManagerBase::LoadTextures() if (bpmem.tevind[i].IsActive() && bpmem.tevind[i].bt < bpmem.genMode.numindstages) usedtextures[bpmem.tevindref.getTexMap(bpmem.tevind[i].bt)] = true; - for (unsigned int i : usedtextures) - g_texture_cache->Load(i); + return usedtextures; +} + +void VertexManagerBase::LoadTextures(const std::vector& textures) +{ + for (auto& texture_info : textures) + g_texture_cache->Load(texture_info); g_texture_cache->BindTextures(); } @@ -452,7 +457,11 @@ void VertexManagerBase::Flush() } // Calculate ZSlope for zfreeze - VertexShaderManager::SetConstants(); + const auto used_textures = UsedTextures(); + std::vector textures; + for (const u32 i : used_textures) + textures.emplace_back(TextureInfo::FromStage(i)); + VertexShaderManager::SetConstants(textures); if (!bpmem.genMode.zfreeze) { // Must be done after VertexShaderManager::SetConstants() @@ -477,7 +486,7 @@ void VertexManagerBase::Flush() // Texture loading can cause palettes to be applied (-> uniforms -> draws). // Palette application does not use vertices, only a full-screen quad, so this is okay. // Same with GPU texture decoding, which uses compute shaders. - LoadTextures(); + LoadTextures(textures); // Now we can upload uniforms, as nothing else will override them. GeometryShaderManager::SetConstants(); diff --git a/Source/Core/VideoCommon/VertexManagerBase.h b/Source/Core/VideoCommon/VertexManagerBase.h index f41be70836ac..a5565ed99eca 100644 --- a/Source/Core/VideoCommon/VertexManagerBase.h +++ b/Source/Core/VideoCommon/VertexManagerBase.h @@ -6,6 +6,7 @@ #include #include +#include "Common/BitSet.h" #include "Common/CommonTypes.h" #include "Common/MathUtil.h" #include "VideoCommon/IndexGenerator.h" @@ -16,6 +17,7 @@ class DataReader; class NativeVertexFormat; class PointerWrap; struct PortableVertexDeclaration; +class TextureInfo; struct Slope { @@ -166,7 +168,9 @@ class VertexManagerBase u32 GetRemainingIndices(int primitive) const; void CalculateZSlope(NativeVertexFormat* format); - void LoadTextures(); + + BitSet32 UsedTextures(); + void LoadTextures(const std::vector& textures); u8* m_cur_buffer_pointer = nullptr; u8* m_base_buffer_pointer = nullptr; diff --git a/Source/Core/VideoCommon/VertexShaderManager.cpp b/Source/Core/VideoCommon/VertexShaderManager.cpp index 88c680632671..0d0d63200af2 100644 --- a/Source/Core/VideoCommon/VertexShaderManager.cpp +++ b/Source/Core/VideoCommon/VertexShaderManager.cpp @@ -133,7 +133,7 @@ void VertexShaderManager::Dirty() // 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() +void VertexShaderManager::SetConstants(const std::vector& textures) { if (constants.missing_color_hex != g_ActiveConfig.iMissingColorValue) { @@ -433,6 +433,8 @@ void VertexShaderManager::SetConstants() if (g_freelook_camera.IsActive() && xfmem.projection.type == ProjectionType::Perspective) corrected_matrix *= g_freelook_camera.GetView(); + g_renderer->GetCustomShaderTriggerManager().OnDraw(xfmem.projection.type, textures); + memcpy(constants.projection.data(), corrected_matrix.data.data(), 4 * sizeof(float4)); g_freelook_camera.SetClean(); diff --git a/Source/Core/VideoCommon/VertexShaderManager.h b/Source/Core/VideoCommon/VertexShaderManager.h index 90df8722accd..1fa2d5dda3b3 100644 --- a/Source/Core/VideoCommon/VertexShaderManager.h +++ b/Source/Core/VideoCommon/VertexShaderManager.h @@ -4,11 +4,13 @@ #pragma once #include +#include #include "Common/CommonTypes.h" #include "VideoCommon/ConstantManager.h" class PointerWrap; +class TextureInfo; // The non-API dependent parts. class VertexShaderManager @@ -19,7 +21,7 @@ class VertexShaderManager static void DoState(PointerWrap& p); // constant management - static void SetConstants(); + static void SetConstants(const std::vector& textures); static void InvalidateXFRange(int start, int end); static void SetTexMatrixChangedA(u32 value); diff --git a/Source/Core/VideoCommon/VideoConfig.cpp b/Source/Core/VideoCommon/VideoConfig.cpp index 70ad7096e975..61e8efeebb70 100644 --- a/Source/Core/VideoCommon/VideoConfig.cpp +++ b/Source/Core/VideoCommon/VideoConfig.cpp @@ -3,8 +3,11 @@ #include +#include + #include "Common/CPUDetect.h" #include "Common/CommonTypes.h" +#include "Common/FileUtil.h" #include "Common/StringUtil.h" #include "Core/Config/GraphicsSettings.h" #include "Core/ConfigManager.h" @@ -223,3 +226,17 @@ u32 VideoConfig::GetShaderPrecompilerThreads() const else return GetNumAutoShaderCompilerThreads(); } + +void VideoConfig::LoadDefaultCustomShaders() +{ + const std::string default_profile_path = + fmt::format("{}/DefaultShaderProfile.json", File::GetUserPath(D_CONFIG_IDX)); + m_trigger_config.LoadFromProfile(default_profile_path); +} + +void VideoConfig::SaveDefaultCustomShaders() const +{ + const std::string default_profile_path = + fmt::format("{}/DefaultShaderProfile.json", File::GetUserPath(D_CONFIG_IDX)); + m_trigger_config.SaveToProfile(default_profile_path); +} diff --git a/Source/Core/VideoCommon/VideoConfig.h b/Source/Core/VideoCommon/VideoConfig.h index cb5e03be2bb4..4bf13d6df342 100644 --- a/Source/Core/VideoCommon/VideoConfig.h +++ b/Source/Core/VideoCommon/VideoConfig.h @@ -13,6 +13,7 @@ #include #include "Common/CommonTypes.h" +#include "VideoCommon/PEShaderSystem/Config/PETriggerConfig.h" enum class APIType; @@ -57,6 +58,9 @@ struct VideoConfig final void Refresh(); void VerifyValidity(); + void LoadDefaultCustomShaders(); + void SaveDefaultCustomShaders() const; + // General bool bVSync; bool bVSyncActive; @@ -77,6 +81,7 @@ struct VideoConfig final bool bDisableCopyFilter; bool bArbitraryMipmapDetection; float fArbitraryMipmapDetectionThreshold; + VideoCommon::PE::TriggerConfig m_trigger_config; // Information bool bShowFPS;