Remove user post-processing shaders and graphics-mod custom shaders

Both fed user-written GLSL to the backends at runtime, which the move to
bytecode emitted straight from the shader generators leaves no compiler
for. The frontend's own shader pipeline covers post-processing.

The presentation pass keeps a plain copy and the built-in correction
shader for output resampling, colour space and gamma correction and HDR
scaling, now compiled in rather than loaded from Sys/Shaders. The
graphics-mod system keeps its draw, EFB, projection and texture actions;
custom pipelines, materials, shader, mesh and sampler assets, the custom
constant buffers in every backend and the two mods built on them are
gone, along with the shader generators' custom-shader hooks.
This commit is contained in:
LibretroAdmin authored and libretroadmin committed 2026-11-05 15:57:56 +01:00
1 parent 767c898df8
commit 472dc5ae3f
142 files changed
+704 -9481

No files matched your search

Whitespace-only changes.
@@ -1,45 +0,0 @@
void process_fragment(in DolphinFragmentInput frag_input, out DolphinFragmentOutput frag_output)
{
int layer = int(frag_input.tex0.z);
int r = int(efb_scale) * int(SPREAD_MULTIPLIER);
int coord;
int length;
int2 initial_coords = int2(frag_input.tex0.xy * source_resolution.xy);
vec4 brightness = float4(1.0, 1.0, 1.0, 1.0);
if (HORIZONTAL)
{
length = int(source_resolution.x);
coord = initial_coords.x;
brightness = BRIGHTNESS_MULTIPLIER * brightness;
}
else
{
length = int(source_resolution.y);
coord = initial_coords.y;
}
int offset;
vec4 count = vec4(0.0,0.0,0.0,0.0);
vec4 col = vec4(0.0,0.0,0.0,0.0);
for (offset = -r; offset <= r; offset++)
{
int pos = coord + offset;
if (pos <= length && pos >= 0)
{
int3 sample_coords;
if (HORIZONTAL)
{
sample_coords = int3(int2(pos, initial_coords.y), layer);
}
else
{
sample_coords = int3(int2(initial_coords.x, pos), layer);
}
col += texelFetch(samp0, sample_coords, 0);
count += vec4(1.0,1.0,1.0,1.0);
}
}
frag_output.main = col / count * brightness;
}
@@ -1,23 +0,0 @@
{
"properties":[
{
"code_name": "HORIZONTAL",
"default": true,
"description": "Whether to apply the blur horizontally or vertically",
"type": "bool"
},
{
"code_name": "SPREAD_MULTIPLIER",
"default": 1.0,
"description": "How much to spread the blur",
"type": "float"
},
{
"code_name": "BRIGHTNESS_MULTIPLIER",
"default": 1.0,
"description": "How bright the blur is",
"type": "float"
}
],
"samplers":[]
}
@@ -1,4 +0,0 @@
void process_vertex(in DolphinVertexInput vertex_input, out DolphinVertexOutput vertex_output)
{
dolphin_process_emulated_vertex(vertex_input, vertex_output);
}
@@ -1,19 +0,0 @@
{
"next_material_asset":"dolphin_bloom_blur_vertical",
"properties":[
{
"type": "bool",
"value": true
},
{
"type": "float",
"value": 1.0
},
{
"type": "float",
"value": 1.0
}
],
"textures":[],
"shader_asset": "dolphin_bloom_blur"
}
@@ -1,19 +0,0 @@
{
"next_material_asset":"",
"properties":[
{
"type": "bool",
"value": false
},
{
"type": "float",
"value": 1.0
},
{
"type": "float",
"value": 1.0
}
],
"textures":[],
"shader_asset": "dolphin_bloom_blur"
}
@@ -1,41 +0,0 @@
{
"meta":
{
"title": "Bloom Blurred",
"author": "Dolphin Team",
"description": "Modifies bloom effects to blur them to their native resolution. Results in bloom looking much more natural at higher resolutions but the blur may be too intense in some games."
},
"features":
[
{
"group": "Bloom",
"action": "custom_pipeline",
"action_data":
{
"material_asset": "dolphin_bloom_blur_horizontal"
}
}
],
"assets": [
{
"data": {
"metadata": "blur.rastershader",
"pixel_shader": "blur.ps.glsl",
"vertex_shader": "blur.vs.glsl"
},
"name": "dolphin_bloom_blur"
},
{
"data": {
"metadata": "blur_horizontal.rastermaterial"
},
"name": "dolphin_bloom_blur_horizontal"
},
{
"data": {
"metadata": "blur_vertical.rastermaterial"
},
"name": "dolphin_bloom_blur_vertical"
}
]
}
Whitespace-only changes.
@@ -1,45 +0,0 @@
void process_fragment(in DolphinFragmentInput frag_input, out DolphinFragmentOutput frag_output)
{
int layer = int(frag_input.tex0.z);
int r = int(efb_scale) * int(SPREAD_MULTIPLIER);
int coord;
int length;
int2 initial_coords = int2(frag_input.tex0.xy * source_resolution.xy);
vec4 brightness = float4(1.0, 1.0, 1.0, 1.0);
if (HORIZONTAL)
{
length = int(source_resolution.x);
coord = initial_coords.x;
brightness = BRIGHTNESS_MULTIPLIER * brightness;
}
else
{
length = int(source_resolution.y);
coord = initial_coords.y;
}
int offset;
vec4 count = vec4(0.0,0.0,0.0,0.0);
vec4 col = vec4(0.0,0.0,0.0,0.0);
for (offset = -r; offset <= r; offset++)
{
int pos = coord + offset;
if (pos <= length && pos >= 0)
{
int3 sample_coords;
if (HORIZONTAL)
{
sample_coords = int3(int2(pos, initial_coords.y), layer);
}
else
{
sample_coords = int3(int2(initial_coords.x, pos), layer);
}
col += texelFetch(samp0, sample_coords, 0);
count += vec4(1.0,1.0,1.0,1.0);
}
}
frag_output.main = col / count * brightness;
}
@@ -1,23 +0,0 @@
{
"properties":[
{
"code_name": "HORIZONTAL",
"default": true,
"description": "Whether to apply the blur horizontally or vertically",
"type": "bool"
},
{
"code_name": "SPREAD_MULTIPLIER",
"default": 1.0,
"description": "How much to spread the blur",
"type": "float"
},
{
"code_name": "BRIGHTNESS_MULTIPLIER",
"default": 1.0,
"description": "How bright the blur is",
"type": "float"
}
],
"samplers":[]
}
@@ -1,4 +0,0 @@
void process_vertex(in DolphinVertexInput vertex_input, out DolphinVertexOutput vertex_output)
{
dolphin_process_emulated_vertex(vertex_input, vertex_output);
}
@@ -1,19 +0,0 @@
{
"next_material_asset":"dolphin_dof_blur_vertical",
"properties":[
{
"type": "bool",
"value": true
},
{
"type": "float",
"value": 1.0
},
{
"type": "float",
"value": 1.0
}
],
"textures":[],
"shader_asset": "dolphin_dof_blur"
}
@@ -1,19 +0,0 @@
{
"next_material_asset":"",
"properties":[
{
"type": "bool",
"value": false
},
{
"type": "float",
"value": 1.0
},
{
"type": "float",
"value": 1.0
}
],
"textures":[],
"shader_asset": "dolphin_dof_blur"
}
@@ -1,41 +0,0 @@
{
"meta":
{
"title": "DOF Blurred",
"author": "Dolphin Team",
"description": "Modifies depth of field effects to blur them to their native resolution. Results in depth of field looking much more natural at higher resolutions but the blur may be too intense in some games."
},
"features":
[
{
"group": "DOF",
"action": "custom_pipeline",
"action_data":
{
"material_asset": "dolphin_dof_blur_horizontal"
}
}
],
"assets": [
{
"data": {
"metadata": "blur.rastershader",
"pixel_shader": "blur.ps.glsl",
"vertex_shader": "blur.vs.glsl"
},
"name": "dolphin_dof_blur"
},
{
"data": {
"metadata": "blur_horizontal.rastermaterial"
},
"name": "dolphin_dof_blur_horizontal"
},
{
"data": {
"metadata": "blur_vertical.rastermaterial"
},
"name": "dolphin_dof_blur_vertical"
}
]
}
-54
View File
@@ -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));
}
-79
View File
@@ -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));
}
@@ -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));
}
@@ -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));
}
-21
View File
@@ -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));
}
-8
View File
@@ -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));
}
-10
View File
@@ -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));
}
-12
View File
@@ -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));
}
-69
View File
@@ -1,69 +0,0 @@
// Based on https://github.com/Filoppi/PumboAutoHDR
/*
[configuration]
[OptionRangeFloat]
GUIName = HDR Display Max Nits
OptionName = HDR_DISPLAY_MAX_NITS
MinValue = 80
MaxValue = 2000
StepAmount = 1
DefaultValue = 400
[OptionRangeFloat]
GUIName = Shoulder Start Alpha
OptionName = AUTO_HDR_SHOULDER_START_ALPHA
MinValue = 0
MaxValue = 1
StepAmount = 0.01
DefaultValue = 0
[OptionRangeFloat]
GUIName = Shoulder Pow
OptionName = AUTO_HDR_SHOULDER_POW
MinValue = 1
MaxValue = 10
StepAmount = 0.05
DefaultValue = 2.5
[/configuration]
*/
float luminance(float3 color)
{
return dot(color, float3(0.2126f, 0.7152f, 0.0722f));
}
void main()
{
float4 color = Sample();
// Nothing to do here, we are in SDR
if (!OptionEnabled(hdr_output) || !OptionEnabled(linear_space_output))
{
SetOutput(color);
return;
}
const float hdr_paper_white = hdr_paper_white_nits / hdr_sdr_white_nits;
// Restore the original SDR (0-1) brightness (we might or might not restore it later)
color.rgb /= hdr_paper_white;
// Find the color luminance (it works better than average)
float sdr_ratio = luminance(color.rgb);
const float auto_hdr_max_white = max(HDR_DISPLAY_MAX_NITS / (hdr_paper_white_nits / hdr_sdr_white_nits), hdr_sdr_white_nits) / hdr_sdr_white_nits;
if (sdr_ratio > AUTO_HDR_SHOULDER_START_ALPHA && AUTO_HDR_SHOULDER_START_ALPHA < 1.0)
{
const float auto_hdr_shoulder_ratio = 1.0 - (max(1.0 - sdr_ratio, 0.0) / (1.0 - AUTO_HDR_SHOULDER_START_ALPHA));
const float auto_hdr_extra_ratio = pow(auto_hdr_shoulder_ratio, AUTO_HDR_SHOULDER_POW) * (auto_hdr_max_white - 1.0);
const float auto_hdr_total_ratio = sdr_ratio + auto_hdr_extra_ratio;
color.rgb *= auto_hdr_total_ratio / sdr_ratio;
}
color.rgb *= hdr_paper_white;
SetOutput(color);
}
-67
View File
@@ -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()));
}
-7
View File
@@ -1,7 +0,0 @@
// Passive (horizontal rows) shader
void main()
{
float screen_row = GetWindowResolution().y * GetCoordinates().y;
SetOutput(SampleLayer(int(screen_row) % 2));
}
-129
View File
@@ -1,129 +0,0 @@
/*
[configuration]
[OptionRangeFloat]
GUIName = Amplificiation
OptionName = AMPLIFICATION
MinValue = 1.0
MaxValue = 6.0
StepAmount = 0.25
DefaultValue = 2.5
[/configuration]
*/
// ICtCP Colorspace as defined by Dolby here:
// https://professional.dolby.com/siteassets/pdfs/ictcp_dolbywhitepaper_v071.pdf
/***** Transfer Function *****/
const float a = 0.17883277;
const float b = 1.0 - 4.0 * a;
const float c = 0.5 - a * log(4.0 * a);
float HLG_f(float x)
{
if (x < 0.0) {
return 0.0;
}
else if (x < 1.0 / 12.0) {
return sqrt(3.0 * x);
}
return a * log(12.0 * x - b) + c;
}
float HLG_inv_f(float x)
{
if (x < 0.0) {
return 0.0;
}
else if (x < 1.0 / 2.0) {
return x * x / 3.0;
}
return (exp((x - c) / a) + b) / 12.0;
}
float4 HLG(float4 lms)
{
return float4(HLG_f(lms.x), HLG_f(lms.y), HLG_f(lms.z), lms.w);
}
float4 HLG_inv(float4 lms)
{
return float4(HLG_inv_f(lms.x), HLG_inv_f(lms.y), HLG_inv_f(lms.z), lms.w);
}
/***** Linear <--> ICtCp *****/
const mat4 RGBtoLMS = mat4(
1688.0, 683.0, 99.0, 0.0,
2146.0, 2951.0, 309.0, 0.0,
262.0, 462.0, 3688.0, 0.0,
0.0, 0.0, 0.0, 4096.0)
/ 4096.0;
const mat4 LMStoICtCp = mat4(
+2048.0, +3625.0, +9500.0, 0.0,
+2048.0, -7465.0, -9212.0, 0.0,
+0.0, +3840.0, -288.0, 0.0,
+0.0, +0.0, +0.0, 4096.0)
/ 4096.0;
float4 LinearRGBToICtCP(float4 c)
{
return LMStoICtCp * HLG(RGBtoLMS * c);
}
/***** ICtCp <--> Linear *****/
mat4 ICtCptoLMS = inverse(LMStoICtCp);
mat4 LMStoRGB = inverse(RGBtoLMS);
float4 ICtCpToLinearRGB(float4 c)
{
return LMStoRGB * HLG_inv(ICtCptoLMS * c);
}
void main()
{
float4 color = Sample();
// Nothing to do here, we are in SDR
if (!OptionEnabled(hdr_output) || !OptionEnabled(linear_space_output)) {
SetOutput(color);
return;
}
// Renormalize Color to be in [0.0 - 1.0] SDR Space. We will revert this later.
const float hdr_paper_white = hdr_paper_white_nits / hdr_sdr_white_nits;
color.rgb /= hdr_paper_white;
// Convert Color to Perceptual Color Space. This will allow us to do perceptual
// scaling while also being able to use the luminance channel.
float4 ictcp_color = LinearRGBToICtCP(color);
// Scale the color in perceptual space depending on the perceived luminance.
//
// At low luminances, ~0.0, pow(AMPLIFICATION, ~0.0) ~= 1.0, so the
// color will appear to be unchanged. This is important as we don't want to
// over expose dark colors which would not have otherwise been seen.
//
// At high luminances, ~1.0, pow(AMPLIFICATION, ~1.0) ~= AMPLIFICATION,
// which is equivalent to scaling the color by AMPLIFICATION. This is
// important as we want to get the most out of the display, and we want to
// get bright colors to hit their target brightness.
//
// For more information, see this desmos demonstrating this scaling process:
// https://www.desmos.com/calculator/syjyrjsj5c
float exposure = length(ictcp_color.xyz);
ictcp_color *= pow(HLG_f(AMPLIFICATION), exposure);
// Convert back to Linear RGB and output the color to the display.
// We use hdr_paper_white to renormalize the color to the comfortable
// SDR viewing range.
SetOutput(hdr_paper_white * ICtCpToLinearRGB(ictcp_color));
}
-16
View File
@@ -1,16 +0,0 @@
void main()
{
float4 c0 = Sample();
float red = 0.0;
float blue = 0.0;
if (c0.r > 0.15 && c0.b > 0.15)
{
blue = 0.5;
red = 0.5;
}
float green = max(c0.r + c0.b, c0.g);
SetOutput(float4(red, green, blue, 1.0));
}
-4
View File
@@ -1,4 +0,0 @@
void main()
{
SetOutput((SampleOffset(int2(1, 1)) - SampleOffset(int2(-1, -1))) * 8.0);
}
-6
View File
@@ -1,6 +0,0 @@
void main()
{
float4 a = SampleOffset(int2( 1, 1));
float4 b = SampleOffset(int2(-1, -1));
SetOutput(( a*a*1.3 - b ) * 8.0);
}
-485
View File
@@ -1,485 +0,0 @@
/*
[configuration]
[OptionBool]
GUIName = Use target window resolution
OptionName = USE_WINDOW_RES
DefaultValue = true
[OptionBool]
GUIName = Debug: Calculate only one character per subgroup
OptionName = DEBUG_ONLY_ONE_CHAR
DefaultValue = false
[/configuration]
*/
const uint MAX_CHARS = 96u; // max 96, must be a multiple of 32
const bool HAVE_FULL_FEATURE_FALLBACK = false; // terrible slow, can easily softlock the GPU
const uint UNROLL_FALLBACK = 4;
const uint UNROLL_SIMD = 3; // max MAX_CHARS / 32
// #undef SUPPORTS_SUBGROUP_REDUCTION
#ifdef API_VULKAN
// By default, subgroupBroadcast only supports compile time constants as index.
// However we need an uniform instead. This is always supported in OpenGL,
// but in Vulkan only in SPIR-V >= 1.5.
// So fall back to subgroupShuffle on Vulkan instead.
#define subgroupBroadcast subgroupShuffle
#endif
/*
The header-only font
We have 96 (ASCII) characters, each of them is 12 pixels high and 8 pixels wide.
To store the boolean value per pixel, 96 bits per character is needed.
So three 32 bit integers are used per character.
This takes in total roughly 1 kB of constant buffer.
The first character must be all-one for the optimized implementation below.
*/
const uint char_width = 8;
const uint char_height = 12;
const uint char_count = 96;
const uint char_pixels = char_width * char_height;
const float2 char_dim = float2(char_width, char_height);
const uint rasters[char_count][(char_pixels + 31) / 32] = {
{0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF}, {0x18181818, 0x00181818, 0x00181800},
{0x6c6c6c6c, 0x00000000, 0x00000000}, {0x66660000, 0xff6666ff, 0x00006666},
{0x1bff7e18, 0xd8f87e1f, 0x00187eff}, {0x6edb1b0e, 0x760c1830, 0x0070d8db},
{0x3333361c, 0x1b0e0e1b, 0x00fe63f3}, {0x18383070, 0x00000000, 0x00000000},
{0x0c0c1830, 0x0c0c0c0c, 0x0030180c}, {0x3030180c, 0x30303030, 0x000c1830},
{0x5a990000, 0x5a3cff3c, 0x00000099}, {0x18180000, 0x18ffff18, 0x00001818},
{0x00000000, 0x38000000, 0x000c1838}, {0x00000000, 0x00ffff00, 0x00000000},
{0x00000000, 0x00000000, 0x00001c1c}, {0x6060c0c0, 0x18183030, 0x06060c0c},
{0xe3c3663c, 0xc7cfdbf3, 0x003c66c3}, {0x181e1c18, 0x18181818, 0x007e1818},
{0x60c0e77e, 0x060c1830, 0x00ff0303}, {0xc0c0e77e, 0xc0e07ee0, 0x007ee7c0},
{0x363c3830, 0x3030ff33, 0x00303030}, {0x030303ff, 0xc0e07f03, 0x007ee7c0},
{0x0303e77e, 0xc3e37f03, 0x007ee7c3}, {0xc0c0c0ff, 0x0c183060, 0x000c0c0c},
{0xc3c3e77e, 0xc3e77ee7, 0x007ee7c3}, {0xc3c3e77e, 0xc0c0fee7, 0x007ee7c0},
{0x00000000, 0x00001c1c, 0x00001c1c}, {0x38000000, 0x38000038, 0x000c1838},
{0x0c183060, 0x0c060306, 0x00603018}, {0x00000000, 0xff00ffff, 0x000000ff},
{0x30180c06, 0x3060c060, 0x00060c18}, {0xc0c3c37e, 0x18183060, 0x00180000},
{0x7e000000, 0xdbcbbbc3, 0x00fc06f3}, {0xc3663c18, 0xc3ffc3c3, 0x00c3c3c3},
{0xc3c3e37f, 0xc3e37fe3, 0x007fe3c3}, {0x0303e77e, 0x03030303, 0x007ee703},
{0xc3e3733f, 0xc3c3c3c3, 0x003f73e3}, {0x030303ff, 0x03033f03, 0x00ff0303},
{0x030303ff, 0x0303033f, 0x00030303}, {0x0303e77e, 0xc3f30303, 0x007ee7c3},
{0xc3c3c3c3, 0xc3c3ffc3, 0x00c3c3c3}, {0x1818187e, 0x18181818, 0x007e1818},
{0x60606060, 0x60606060, 0x003e7763}, {0x1b3363c3, 0x1b0f070f, 0x00c36333},
{0x03030303, 0x03030303, 0x00ff0303}, {0xffffe7c3, 0xc3c3c3db, 0x00c3c3c3},
{0xcfcfc7c7, 0xf3fbdbdf, 0x00e3e3f3}, {0xc3c3e77e, 0xc3c3c3c3, 0x007ee7c3},
{0xc3c3e37f, 0x03037fe3, 0x00030303}, {0xc3c3663c, 0xdbc3c3c3, 0x00fc76fb},
{0xc3c3e37f, 0x1b0f7fe3, 0x00c36333}, {0x0303e77e, 0xc0e07e07, 0x007ee7c0},
{0x181818ff, 0x18181818, 0x00181818}, {0xc3c3c3c3, 0xc3c3c3c3, 0x007ee7c3},
{0xc3c3c3c3, 0x6666c3c3, 0x00183c3c}, {0xc3c3c3c3, 0xffdbdbc3, 0x00c3e7ff},
{0x3c6666c3, 0x3c3c183c, 0x00c36666}, {0x3c6666c3, 0x1818183c, 0x00181818},
{0x60c0c0ff, 0x060c7e30, 0x00ff0303}, {0x0c0c0c3c, 0x0c0c0c0c, 0x003c0c0c},
{0x0c0c0606, 0x30301818, 0xc0c06060}, {0x3030303c, 0x30303030, 0x003c3030},
{0xc3663c18, 0x00000000, 0x00000000}, {0x00000000, 0x00000000, 0xff000000},
{0x181c0c0e, 0x00000000, 0x00000000}, {0x00000000, 0xfec0c37e, 0x00fec3c3},
{0x03030303, 0xc3c37f03, 0x007fc3c3}, {0x00000000, 0x0303c37e, 0x007ec303},
{0xc0c0c0c0, 0xc3c3fec0, 0x00fec3c3}, {0x00000000, 0x7fc3c37e, 0x00fe0303},
{0x0c0ccc78, 0x0c0c3f0c, 0x000c0c0c}, {0x00000000, 0xc3c3c37e, 0xc3c0c0fe},
{0x03030303, 0xc3c3c37f, 0x00c3c3c3}, {0x00001800, 0x18181818, 0x00181818},
{0x00003000, 0x30303030, 0x36303030}, {0x03030303, 0x0f1b3363, 0x0063331f},
{0x1818181e, 0x18181818, 0x007e1818}, {0x00000000, 0xdbdbdb7f, 0x00dbdbdb},
{0x00000000, 0x6363633f, 0x00636363}, {0x00000000, 0x6363633e, 0x003e6363},
{0x00000000, 0xc3c3c37f, 0x03037fc3}, {0x00000000, 0xc3c3c3fe, 0xc0c0fec3},
{0x00000000, 0x0303077f, 0x00030303}, {0x00000000, 0x7e0303fe, 0x007fc0c0},
{0x0c0c0c00, 0x0c0c0c3f, 0x00386c0c}, {0x00000000, 0x63636363, 0x007e6363},
{0x00000000, 0x6666c3c3, 0x00183c3c}, {0x00000000, 0xdbc3c3c3, 0x00c3e7ff},
{0x00000000, 0x183c66c3, 0x00c3663c}, {0x00000000, 0x3c6666c3, 0x06060c18},
{0x00000000, 0x183060ff, 0x00ff060c}, {0x181818f0, 0x181c0f1c, 0x00f01818},
{0x18181818, 0x18181818, 0x18181818}, {0x1818180f, 0x1838f038, 0x000f1818},
{0x06000000, 0x0060f18f, 0x00000000}, {0x00000000, 0x00000000, 0x00000000}};
// Precalculated sum of all pixels per character
const uint raster_active_pixels[char_count] = {
96, 18, 16, 40, 56, 42, 46, 10, 22, 22, 32, 28, 10, 16, 6, 24, 52, 29, 36, 44, 35, 42, 50, 28,
58, 51, 12, 16, 22, 32, 22, 26, 41, 46, 57, 38, 52, 38, 32, 46, 48, 30, 31, 43, 28, 56, 64, 52,
42, 52, 52, 44, 28, 48, 42, 58, 42, 32, 38, 26, 24, 26, 14, 8, 10, 34, 40, 26, 40, 32, 30, 33,
39, 16, 20, 37, 28, 43, 30, 30, 34, 34, 20, 28, 27, 30, 26, 36, 26, 24, 26, 30, 24, 30, 14, 0};
// Get one sample of the font: (pixel index, character index)
float SampleFont(uint2 pos)
{
return (rasters[pos.y][pos.x / 32] >> (pos.x % 32)) & uint(1);
}
// Get one sample of the framebuffer: (character position in screen space, pixel index)
float3 SampleTex(uint2 char_pos, uint pixel)
{
float2 inv_resoltion =
OptionEnabled(USE_WINDOW_RES) ? GetInvWindowResolution() : GetInvResolution();
float2 tex_pos = char_pos * char_dim + float2(pixel % char_width, pixel / char_width) + 0.5;
return SampleLocation(tex_pos * inv_resoltion).xyz;
}
struct CharResults
{
float3 fg; // font color
float3 bg; // background color
float err; // MSE of this configuration
uint c; // character index
};
// Calculate the font and background color and the MSE for a given character
CharResults CalcCharRes(uint c, float3 t, float3 ft)
{
CharResults o;
o.c = c;
// Inputs:
// tt: sum of all texture samples squared
// t: sum of all texture samples
// ff: sum of all font samples squared
// f: sum of all font samples
// ft: sum of all font samples * texture samples
// The font is either 1.0 or 0.0, so ff == f
// As the font is constant, this is pre-calculated
float f = raster_active_pixels[c];
float ff = f;
// The calculation isn't stable if the font is all-one. Return max err
// instead.
if (f == char_pixels)
{
o.err = char_pixels * char_pixels;
return o;
}
// tt is only used as constant offset for the error, define it as zero
float3 tt = float3(0.0, 0.0, 0.0);
// The next lines are a bit harder, hf :-)
// The idea is to find the perfect char with the perfect background color
// and the perfect font color. As this is an equation with three unknowns,
// we can't just try all chars and color combinations.
// As criterion how "perfect" the selection is, we compare the "mean
// squared error" of the resulted colors of all chars. So, now the big
// issue: how to calculate the MSE without knowing the two colors ...
// In the next steps, "a" is the font color, "b" is the background color,
// "f" is the font value at this pixel, "t" is the texture value
// So the square error of one pixel is:
// e = ( t - a⋅f - b⋅(1-f) ) ^ 2
// In longer:
// e = a^2⋅f^2 - 2⋅a⋅b⋅f^2 + 2⋅a⋅b⋅f - 2⋅a⋅f⋅t + b^2⋅f^2 - 2⋅b^2⋅f + b^2 +
// 2⋅b⋅f⋅t - 2⋅b⋅t + t^2
// The sum of all errors is: (as shortcut, ff,f,ft,t,tt are now the sums
// like declared above, sum(1) is the count of pixels) sum(e) = a^2⋅ff -
// 2⋅a^2⋅ff + 2⋅a⋅b⋅f - 2⋅a⋅ft + b^2⋅ff - 2⋅b^2⋅f + b^2⋅sum(1) + 2⋅b⋅ft -
// 2⋅b⋅t + tt
// tt is only used as a constant offset, so its value has no effect on a,b or
// on the relative error. So it can be completely dropped.
// To find the minimum, we have to derive this by "a" and "b":
// d/da sum(e) = 2⋅a⋅ff + 2⋅b⋅f - 2⋅b⋅ff - 2⋅ft
// d/db sum(e) = 2⋅a⋅f - 2⋅a⋅ff - 4⋅b⋅f + 2⋅b⋅ff + 2⋅b⋅sum(1) + 2⋅ft - 2⋅t
// So, both equations must be zero at minimum and there is only one
// solution.
float3 a = (ft * (f - float(char_pixels)) + t * (f - ff)) / (f * f - ff * float(char_pixels));
float3 b = (ft * f - t * ff) / (f * f - ff * float(char_pixels));
float3 e = a * a * ff + 2.0 * a * b * (f - ff) - 2.0 * a * ft +
b * b * (-2.0 * f + ff + float(char_pixels)) + 2.0 * b * ft - 2.0 * b * t + tt;
o.err = dot(e, float3(1.0, 1.0, 1.0));
o.fg = a;
o.bg = b;
o.c = c;
return o;
}
// Get the color of the pixel of this invocation based on the character details
float3 GetFinalPixel(CharResults char_out)
{
float2 resolution = OptionEnabled(USE_WINDOW_RES) ? GetWindowResolution() : GetResolution();
uint2 char_pos = uint2(floor(GetCoordinates() * resolution / char_dim));
uint2 pixel_offset = uint2(floor(GetCoordinates() * resolution) - char_pos * char_dim);
float font = SampleFont(int2(pixel_offset.x + char_width * pixel_offset.y, char_out.c));
return char_out.fg * font + char_out.bg * (1.0 - font);
}
/*
This shader performs some kind of brute force evaluation, which character fits best.
for c in characters:
for p in pixels:
ft += font(c,p) * texture(p)
res = CalcCharRes(ft)
min(res.err)
Terrible in performance, only for reference.
*/
CharResults CalcCharTrivial(uint2 char_pos)
{
float3 t;
CharResults char_out;
char_out.err = char_pixels * char_pixels;
for (uint c = 0; c < MAX_CHARS; c += 1)
{
float3 ft = float3(0.0, 0.0, 0.0);
for (uint pixel = 0; pixel < char_pixels; pixel += 1)
{
float3 tex = SampleTex(char_pos, pixel);
float font = SampleFont(uint2(pixel, c));
ft += font * tex;
}
if (c == 0)
t = ft;
CharResults res = CalcCharRes(c, t, ft);
if (res.err < char_out.err)
char_out = res;
}
return char_out;
}
/*
However for better performance, some characters are tested at once. This saves some expensive
texture() calls. Also split the loop over the pixels in groups of 32 for only fetching the uint32
of the font once.
*/
CharResults CalcCharFallback(uint2 char_pos)
{
float3 t;
CharResults char_out;
char_out.err = char_pixels * char_pixels;
for (uint c = 0; c < MAX_CHARS; c += UNROLL_FALLBACK)
{
// Declare ft
float3 ft[UNROLL_FALLBACK];
for (uint i = 0; i < UNROLL_FALLBACK; i++)
ft[i] = float3(0.0, 0.0, 0.0);
// Split `for p : pixels` in groups of 32. This makes accessing the texture (bit in uint32)
// easier.
for (uint pixel = 0; pixel < char_pixels; pixel += 32)
{
uint font_i[UNROLL_FALLBACK];
for (uint i = 0; i < UNROLL_FALLBACK; i++)
font_i[i] = rasters[c + i][pixel / 32];
for (uint pixel_offset = 0; pixel_offset < 32; pixel_offset += 1)
{
float3 tex = SampleTex(char_pos, pixel + pixel_offset);
// Inner kernel of `ft += font * tex`. Most time is spend in here.
for (uint i = 0; i < UNROLL_FALLBACK; i++)
{
float font = (font_i[i] >> pixel_offset) & uint(1);
ft[i] += font * tex;
}
}
}
if (c == 0)
{
// First char has font := 1, so t = ft. Cache this value for the next iterations.
t = ft[0];
}
// Check if this character fits better than the last one.
for (uint i = 0; i < UNROLL_FALLBACK; i++)
{
CharResults res = CalcCharRes(c + i, t, ft[i]);
if (res.err < char_out.err)
char_out = res;
}
}
return char_out;
}
/*
SIMD optimized version with subgroup intrinsics
- distribute all characters over the lanes and check for them in parallel
- distribute the uniform texture access and broadcast each back to each lane
*/
CharResults CalcCharSIMD(uint2 char_pos, uint simd_width)
{
// Font color, bg color, character, error -- of character with minimum error
CharResults char_out;
char_out.err = char_pixels * char_pixels;
float3 t;
#ifdef SUPPORTS_SUBGROUP_REDUCTION
// Hack: Work in hard-codeded fixed SIMD mode
if (gl_SubgroupInvocationID < simd_width)
{
// Loop over all characters
for (uint c = 0; c < MAX_CHARS; c += UNROLL_SIMD * simd_width)
{
// registers for "sum of font * texture"
float3 ft[UNROLL_SIMD];
for (uint i = 0; i < UNROLL_SIMD; i++)
ft[i] = float3(0.0, 0.0, 0.0);
for (uint pixel = 0; pixel < char_pixels; pixel += 32)
{
// Preload the font uint32 for the next 32 pixels
uint font_i[UNROLL_SIMD];
for (uint i = 0; i < UNROLL_SIMD; i++)
font_i[i] = rasters[c + UNROLL_SIMD * gl_SubgroupInvocationID + i][pixel / 32];
for (uint pixel_offset = 0; pixel_offset < 32; pixel_offset += simd_width)
{
// Copy one full WRAP of textures into registers and shuffle them around for later usage.
// This avoids one memory transaction per tested pixel & character.
float3 tex_simd = SampleTex(char_pos, pixel + pixel_offset + gl_SubgroupInvocationID);
for (uint k = 0; k < simd_width; k += 1)
{
float3 tex = subgroupBroadcast(tex_simd, k);
// Note: As pixel iterates based on power-of-two gl_SubgroupSize,
// the const memory access to rasters is CSE'd and the inner loop
// after unrolling only contains: testing one bit + shuffle +
// conditional add
for (uint i = 0; i < UNROLL_SIMD; i++)
{
float font = (font_i[i] >> (k + pixel_offset % 32)) & uint(1);
ft[i] += font * tex;
}
}
}
}
if (c == 0)
{
// font[0] is a hardcoded 1 font, so t = ft
t = subgroupBroadcast(ft[0], 0);
}
for (uint i = 0; i < UNROLL_SIMD; i++)
{
CharResults res = CalcCharRes(c + UNROLL_SIMD * gl_SubgroupInvocationID + i, t, ft[i]);
if (res.err < char_out.err)
char_out = res;
}
}
}
// Broadcast to get the best character of all threads
float err_min = subgroupMin(char_out.err);
uint smallest = subgroupBallotFindLSB(subgroupBallot(err_min == char_out.err));
char_out.fg = subgroupBroadcast(char_out.fg, smallest);
char_out.bg = subgroupBroadcast(char_out.bg, smallest);
char_out.c = subgroupBroadcast(char_out.c, smallest);
char_out.err = err_min;
#endif
return char_out;
}
bool supportsSIMD(uint simd_width)
{
#ifdef SUPPORTS_SUBGROUP_REDUCTION
const uint mask = simd_width == 32u ? 0xFFFFFFFFu : (1u << simd_width) - 1;
return (subgroupBallot(true)[0] & mask) == mask;
#else
return false;
#endif
}
// "Error: The AsciiArt shader requires the missing GPU extension KHR_shader_subgroup."
const uint missing_subgroup_warning_len = 82;
const uint missing_subgroup_warning[missing_subgroup_warning_len] = {
37, 82, 82, 79, 82, 26, 95, 52, 72, 69, 95, 33, 83, 67, 73, 73, 33, 82, 84, 95, 83,
72, 65, 68, 69, 82, 95, 82, 69, 81, 85, 73, 82, 69, 83, 95, 84, 72, 69, 95, 77, 73,
83, 83, 73, 78, 71, 95, 39, 48, 53, 95, 69, 88, 84, 69, 78, 84, 73, 79, 78, 95, 43,
40, 50, 63, 83, 72, 65, 68, 69, 82, 63, 83, 85, 66, 71, 82, 79, 85, 80, 14};
float3 ShowWarning(uint2 char_pos)
{
CharResults char_out;
char_out.fg = float3(1.0, 1.0, 1.0);
char_out.bg = float3(0.0, 0.0, 0.0);
char_out.c = 95u; // just background
if (char_pos.y == 0u && char_pos.x < missing_subgroup_warning_len)
{
char_out.c = missing_subgroup_warning[char_pos.x];
}
return GetFinalPixel(char_out);
}
void main()
{
// Calculate the character position of this pixel
float2 resolution = OptionEnabled(USE_WINDOW_RES) ? GetWindowResolution() : GetResolution();
uint2 char_pos_self = uint2(floor(GetCoordinates() * resolution / char_dim));
float3 color_out;
#ifdef SUPPORTS_SUBGROUP_REDUCTION
if (supportsSIMD(8))
{
// Loop over all character positions covered by this wave
bool pixel_active = !gl_HelperInvocation;
CharResults char_out;
while (true)
{
// Fetch the next active character position
uint4 active_lanes = subgroupBallot(pixel_active);
if (active_lanes == uint4(0, 0, 0, 0))
{
break;
}
uint2 char_pos = subgroupBroadcast(char_pos_self, subgroupBallotFindLSB(active_lanes));
// And calculate everything for this character position
if (supportsSIMD(32))
{
char_out = CalcCharSIMD(char_pos, 32);
}
else if (supportsSIMD(16))
{
char_out = CalcCharSIMD(char_pos, 16);
}
else if (supportsSIMD(8))
{
char_out = CalcCharSIMD(char_pos, 8);
}
// Draw the character on screen
if (char_pos == char_pos_self)
{
color_out = GetFinalPixel(char_out);
pixel_active = false;
}
if (OptionEnabled(DEBUG_ONLY_ONE_CHAR))
{
break;
}
}
}
else
#else
if (char_pos_self.y <= 1u)
{
color_out = ShowWarning(char_pos_self);
}
else
#endif
if (HAVE_FULL_FEATURE_FALLBACK)
{
color_out = GetFinalPixel(CalcCharFallback(char_pos_self));
}
else
{
color_out = Sample().xyz;
}
SetOutput(float4(color_out, 1.0));
}
-15
View File
@@ -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);
}
-88
View File
@@ -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));
}
-36
View File
@@ -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);
}
-4
View File
@@ -1,4 +0,0 @@
void main()
{
SetOutput(Sample()* 3.0);
}
-13
View File
@@ -1,13 +0,0 @@
void main()
{
float4 c0 = Sample();
float red = 0.0;
float green = 0.0;
if (c0.r < 0.35 || c0.b > 0.35)
green = c0.g + (c0.b / 2.0);
else
red = c0.r + 0.4;
SetOutput(float4(red, green, 0.0, 1.0));
}
-20
View File
@@ -1,20 +0,0 @@
void main()
{
float4 c0 = Sample();
float red = 0.0;
float green = 0.0;
float blue = 0.0;
if (c0.r < 0.50 || c0.b > 0.5)
{
blue = c0.r;
red = c0.g;
}
else
{
blue = c0.r;
green = c0.r;
}
SetOutput(float4(red, green, blue, 1.0));
}
-33
View File
@@ -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));
}
@@ -1,476 +0,0 @@
/***** COLOR CORRECTION *****/
// Color Space references:
// https://www.unravel.com.au/understanding-color-spaces
// SMPTE 170M - BT.601 (NTSC-M) -> BT.709
mat3 from_NTSCM = transpose(mat3(
0.939497225737661, 0.0502268452914346, 0.0102759289709032,
0.0177558637510127, 0.965824605885027, 0.0164195303639603,
-0.00162163209967010, -0.00437400622653655, 1.00599563832621));
// ARIB TR-B9 (9300K+27MPCD with chromatic adaptation) (NTSC-J) -> BT.709
mat3 from_NTSCJ = transpose(mat3(
0.768497526, -0.210804164, 0.000297427177,
0.0397904068, 1.04825413, 0.00555809540,
0.00147510506, 0.0328789241, 1.36515128));
// EBU - BT.470BG/BT.601 (PAL) -> BT.709
mat3 from_PAL = transpose(mat3(
1.04408168421813, -0.0440816842181253, 0.000000000000000,
0.000000000000000, 1.00000000000000, 0.000000000000000,
0.000000000000000, 0.0118044782106489, 0.988195521789351));
float3 LinearTosRGBGamma(float3 color)
{
const float a = 0.055;
for (int i = 0; i < 3; ++i)
{
float x = color[i];
if (x <= 0.0031308)
x = x * 12.92;
else
x = (1.0 + a) * pow(x, 1.0 / 2.4) - a;
color[i] = x;
}
return color;
}
float3 Rec709_Luminance = float3(0.2126, 0.7152, 0.0722);
float Luminance(float3 color, bool native_color_space)
{
if (native_color_space && OptionEnabled(correct_color_space))
{
if (game_color_space == 0)
color *= from_NTSCM;
else if (game_color_space == 1)
color *= from_NTSCJ;
else if (game_color_space == 2)
color *= from_PAL;
}
return dot(color, Rec709_Luminance);
}
/***** COLOR SAMPLING *****/
// Non filtered gamma corrected sample (nearest neighbor)
float4 QuickSample(float3 uvw, float gamma)
{
#if 0 // Test sampling range
const float threshold = 0.00000001;
float2 xy = uvw.xy * GetResolution();
// Sampling outside the valid range, draw in yellow
if (xy.x < (0.0 - threshold) || xy.x > (GetResolution().x + threshold) || xy.y < (0.0 - threshold) || xy.y > (GetResolution().y + threshold))
return float4(1.0, 1.0, 0.0, 1);
// Sampling at the edges, draw in purple
if (xy.x < 1.0 || xy.x > (GetResolution().x - 1.0) || xy.y < 1.0 || xy.y > (GetResolution().y - 1.0))
return float4(0.5, 0, 0.5, 1);
#endif
float4 color = texture(samp1, uvw);
color.rgb = pow(color.rgb, float3(gamma));
return color;
}
float4 QuickSample(float2 uv, float w, float gamma)
{
return QuickSample(float3(uv, w), gamma);
}
float4 QuickSampleByPixel(float2 xy, float w, float gamma)
{
float3 uvw = float3(xy * GetInvResolution(), w);
return QuickSample(uvw, gamma);
}
/***** Bilinear Interpolation *****/
float4 BilinearSample(float3 uvw, float gamma)
{
// This emulates the (bi)linear filtering done directly from GPUs HW.
// Note that GPUs might natively filter red green and blue differently, but we don't do it.
// They might also use different filtering between upscaling and downscaling.
float2 source_size = GetResolution();
float2 pixel = (uvw.xy * source_size) - 0.5; // Try to find the matching pixel top left corner
// Find the integer and floating point parts
float2 int_pixel = floor(pixel);
float2 frac_pixel = fract(pixel);
// Take 4 samples around the original uvw
float4 c11 = QuickSampleByPixel(int_pixel + float2(0.5, 0.5), uvw.z, gamma);
float4 c21 = QuickSampleByPixel(int_pixel + float2(1.5, 0.5), uvw.z, gamma);
float4 c12 = QuickSampleByPixel(int_pixel + float2(0.5, 1.5), uvw.z, gamma);
float4 c22 = QuickSampleByPixel(int_pixel + float2(1.5, 1.5), uvw.z, gamma);
// Blend the 4 samples by their weight
float4 avg_c = lerp(lerp(c11, c21, frac_pixel.x), lerp(c12, c22, frac_pixel.x), frac_pixel.y);
// Calculate the average of the gamma space luminance, as that can be used to
// properly determine the perceptual brightness the color should have.
float l11 = pow(Luminance(c11.rgb, true), 1.0 / gamma);
float l21 = pow(Luminance(c21.rgb, true), 1.0 / gamma);
float l12 = pow(Luminance(c12.rgb, true), 1.0 / gamma);
float l22 = pow(Luminance(c22.rgb, true), 1.0 / gamma);
float target_avg_c_lum = lerp(lerp(l11, l21, frac_pixel.x), lerp(l12, l22, frac_pixel.x), frac_pixel.y);
float avg_c_lum = Luminance(avg_c.rgb, true);
if (avg_c_lum != 0.0)
{
avg_c.rgb *= pow(target_avg_c_lum, gamma) / avg_c_lum;
}
return avg_c;
}
/***** Bicubic Interpolation *****/
// Formula derived from:
// https://en.wikipedia.org/wiki/Mitchell%E2%80%93Netravali_filters#Definition
// Values from:
// https://guideencodemoe-mkdocs.readthedocs.io/encoding/resampling/#mitchell-netravali-bicubic
// Other references:
// https://www.codeproject.com/Articles/236394/Bi-Cubic-and-Bi-Linear-Interpolation-with-GLSL
// https://github.com/ValveSoftware/gamescope/pull/740
// https://stackoverflow.com/questions/13501081/efficient-bicubic-filtering-code-in-glsl
#define CUBIC_COEFF_GEN(B, C) \
(mat4(/* t^0 */ ((B) / 6.0), (-(B) / 3.0 + 1.0), ((B) / 6.0), (0.0), \
/* t^1 */ (-(B) / 2.0 - (C)), (0.0), ((B) / 2.0 + (C)), (0.0), \
/* t^2 */ ((B) / 2.0 + 2.0 * (C)), (2.0 * (B) + (C)-3.0), \
(-5.0 * (B) / 2.0 - 2.0 * (C) + 3.0), (-(C)), \
/* t^3 */ (-(B) / 6.0 - (C)), (-3.0 * (B) / 2.0 - (C) + 2.0), \
(3.0 * (B) / 2.0 + (C)-2.0), ((B) / 6.0 + (C))))
float4 CubicCoeffs(float t, mat4 coeffs)
{
return coeffs * float4(1.0, t, t * t, t * t * t);
}
float4 CubicMix(float4 c0, float4 c1, float4 c2, float4 c3, float4 coeffs)
{
return c0 * coeffs[0] + c1 * coeffs[1] + c2 * coeffs[2] + c3 * coeffs[3];
}
// By Sam Belliveau. Public Domain license.
// Simple 16 tap, gamma correct, implementation of bicubic filtering.
float4 BicubicSample(float3 uvw, float gamma, mat4 coeffs)
{
float2 pixel = (uvw.xy * GetResolution()) - 0.5;
float2 int_pixel = floor(pixel);
float2 frac_pixel = fract(pixel);
float4 c00 = QuickSampleByPixel(int_pixel + float2(-0.5, -0.5), uvw.z, gamma);
float4 c10 = QuickSampleByPixel(int_pixel + float2(+0.5, -0.5), uvw.z, gamma);
float4 c20 = QuickSampleByPixel(int_pixel + float2(+1.5, -0.5), uvw.z, gamma);
float4 c30 = QuickSampleByPixel(int_pixel + float2(+2.5, -0.5), uvw.z, gamma);
float4 c01 = QuickSampleByPixel(int_pixel + float2(-0.5, +0.5), uvw.z, gamma);
float4 c11 = QuickSampleByPixel(int_pixel + float2(+0.5, +0.5), uvw.z, gamma);
float4 c21 = QuickSampleByPixel(int_pixel + float2(+1.5, +0.5), uvw.z, gamma);
float4 c31 = QuickSampleByPixel(int_pixel + float2(+2.5, +0.5), uvw.z, gamma);
float4 c02 = QuickSampleByPixel(int_pixel + float2(-0.5, +1.5), uvw.z, gamma);
float4 c12 = QuickSampleByPixel(int_pixel + float2(+0.5, +1.5), uvw.z, gamma);
float4 c22 = QuickSampleByPixel(int_pixel + float2(+1.5, +1.5), uvw.z, gamma);
float4 c32 = QuickSampleByPixel(int_pixel + float2(+2.5, +1.5), uvw.z, gamma);
float4 c03 = QuickSampleByPixel(int_pixel + float2(-0.5, +2.5), uvw.z, gamma);
float4 c13 = QuickSampleByPixel(int_pixel + float2(+0.5, +2.5), uvw.z, gamma);
float4 c23 = QuickSampleByPixel(int_pixel + float2(+1.5, +2.5), uvw.z, gamma);
float4 c33 = QuickSampleByPixel(int_pixel + float2(+2.5, +2.5), uvw.z, gamma);
float4 cx = CubicCoeffs(frac_pixel.x, coeffs);
float4 cy = CubicCoeffs(frac_pixel.y, coeffs);
float4 x0 = CubicMix(c00, c10, c20, c30, cx);
float4 x1 = CubicMix(c01, c11, c21, c31, cx);
float4 x2 = CubicMix(c02, c12, c22, c32, cx);
float4 x3 = CubicMix(c03, c13, c23, c33, cx);
return CubicMix(x0, x1, x2, x3, cy);
}
/***** Sharp Bilinear Filtering *****/
// Based on https://github.com/libretro/slang-shaders/blob/master/interpolation/shaders/sharp-bilinear.slang
// by Themaister, Public Domain license
// Does a bilinear stretch, with a preapplied Nx nearest-neighbor scale,
// giving a sharper image than plain bilinear.
float4 SharpBilinearSample(float3 uvw, float gamma)
{
float2 source_size = GetResolution();
float2 inverted_source_size = GetInvResolution();
float2 target_size = GetWindowResolution();
float2 texel = uvw.xy * source_size;
float2 texel_floored = floor(texel);
float2 s = fract(texel);
float scale = max(floor(max(target_size.x * inverted_source_size.x, target_size.y * inverted_source_size.y)), 1.0);
float region_range = 0.5 - (0.5 / scale);
// Figure out where in the texel to sample to get correct pre-scaled bilinear.
float2 center_dist = s - 0.5;
float2 f = ((center_dist - clamp(center_dist, -region_range, region_range)) * scale) + 0.5;
float2 mod_texel = texel_floored + f;
uvw.xy = mod_texel * inverted_source_size;
return BilinearSample(uvw, gamma);
}
/***** Area Sampling *****/
// By Sam Belliveau and Filippo Tarpini. Public Domain license.
// Effectively a more accurate sharp bilinear filter when upscaling,
// that also works as a mathematically perfect downscale filter.
// https://entropymine.com/imageworsener/pixelmixing/
// https://github.com/obsproject/obs-studio/pull/1715
// https://legacy.imagemagick.org/Usage/filter/
float4 AreaSampling(float3 uvw, float gamma)
{
// Determine the sizes of the source and target images.
float2 source_size = GetResolution();
float2 target_size = GetWindowResolution();
float2 inverted_target_size = GetInvWindowResolution();
// Compute the top-left and bottom-right corners of the target pixel box.
float2 t_beg = floor(uvw.xy * target_size);
float2 t_end = t_beg + float2(1.0, 1.0);
// Convert the target pixel box to source pixel box.
float2 beg = t_beg * inverted_target_size * source_size;
float2 end = t_end * inverted_target_size * source_size;
// Compute the top-left and bottom-right corners of the pixel box.
float2 f_beg = floor(beg);
float2 f_end = floor(end);
// Compute how much of the start and end pixels are covered horizontally & vertically.
float area_w = 1.0 - fract(beg.x);
float area_n = 1.0 - fract(beg.y);
float area_e = fract(end.x);
float area_s = fract(end.y);
// Compute the areas of the corner pixels in the pixel box.
float area_nw = area_n * area_w;
float area_ne = area_n * area_e;
float area_sw = area_s * area_w;
float area_se = area_s * area_e;
// Initialize the color accumulator.
float4 avg_color = float4(0.0, 0.0, 0.0, 0.0);
float avg_luminance = 0.0;
float4 temp_color;
float luminance_gamma = gamma; // For now, default to the user selected gamma (alternatively we could fix it to anything between 2.2 and 3.0)
float luminance_inv_gamma = 1.0 / luminance_gamma;
// Prevents rounding errors due to the coordinates flooring above
const float2 offset = float2(0.5, 0.5);
// Accumulate corner pixels.
temp_color = QuickSampleByPixel(float2(f_beg.x, f_beg.y) + offset, uvw.z, gamma);
avg_color += area_nw * temp_color;
avg_luminance += area_nw * pow(Luminance(temp_color.rgb, true), luminance_inv_gamma);
temp_color = QuickSampleByPixel(float2(f_end.x, f_beg.y) + offset, uvw.z, gamma);
avg_color += area_ne * temp_color;
avg_luminance += area_ne * pow(Luminance(temp_color.rgb, true), luminance_inv_gamma);
temp_color = QuickSampleByPixel(float2(f_beg.x, f_end.y) + offset, uvw.z, gamma);
avg_color += area_sw * temp_color;
avg_luminance += area_sw * pow(Luminance(temp_color.rgb, true), luminance_inv_gamma);
temp_color = QuickSampleByPixel(float2(f_end.x, f_end.y) + offset, uvw.z, gamma);
avg_color += area_se * temp_color;
avg_luminance += area_se * pow(Luminance(temp_color.rgb, true), luminance_inv_gamma);
// Determine the size of the pixel box.
int x_range = int(f_end.x - f_beg.x - 0.5);
int y_range = int(f_end.y - f_beg.y - 0.5);
// Workaround to compile the shader with DX11/12.
// If this isn't done, it will complain that the loop could have too many iterations.
// This number should be enough to guarantee downscaling from very high to very small resolutions.
// Note that this number might be referenced in the UI.
const int max_iterations = 16;
// Fix up the average calculations in case we reached the upper limit
x_range = min(x_range, max_iterations);
y_range = min(y_range, max_iterations);
// Accumulate top and bottom edge pixels.
for (int ix = 0; ix < max_iterations; ++ix)
{
if (ix < x_range)
{
float x = f_beg.x + 1.0 + float(ix);
temp_color = QuickSampleByPixel(float2(x, f_beg.y) + offset, uvw.z, gamma);
avg_color += area_n * temp_color;
avg_luminance += area_n * pow(Luminance(temp_color.rgb, true), luminance_inv_gamma);
temp_color = QuickSampleByPixel(float2(x, f_end.y) + offset, uvw.z, gamma);
avg_color += area_s * temp_color;
avg_luminance += area_s * pow(Luminance(temp_color.rgb, true), luminance_inv_gamma);
}
}
// Accumulate left and right edge pixels and all the pixels in between.
for (int iy = 0; iy < max_iterations; ++iy)
{
if (iy < y_range)
{
float y = f_beg.y + 1.0 + float(iy);
temp_color = QuickSampleByPixel(float2(f_beg.x, y) + offset, uvw.z, gamma);
avg_color += area_w * temp_color;
avg_luminance += area_w * pow(Luminance(temp_color.rgb, true), luminance_inv_gamma);
temp_color = QuickSampleByPixel(float2(f_end.x, y) + offset, uvw.z, gamma);
avg_color += area_e * temp_color;
avg_luminance += area_e * pow(Luminance(temp_color.rgb, true), luminance_inv_gamma);
for (int ix = 0; ix < max_iterations; ++ix)
{
if (ix < x_range)
{
float x = f_beg.x + 1.0 + float(ix);
temp_color = QuickSampleByPixel(float2(x, y) + offset, uvw.z, gamma);
avg_color += temp_color;
avg_luminance += pow(Luminance(temp_color.rgb, true), luminance_inv_gamma);
}
}
}
}
// Compute the area of the pixel box that was sampled.
float area_corners = area_nw + area_ne + area_sw + area_se;
float area_edges = float(x_range) * (area_n + area_s) + float(y_range) * (area_w + area_e);
float area_center = float(x_range) * float(y_range);
float4 nrm_color = avg_color / (area_corners + area_edges + area_center);
float target_nrm_color_luminance = avg_luminance / (area_corners + area_edges + area_center);
// Restore the averaged "gamma" space luminance, for better gamma correction.
// This retains the best feature of gamma correct sampling (no hue shifts),
// while also maintaining the perceptual "brightness" level of blending two colors with an alpha
// (in linear space a 0.5 alpha won't produce a color that has a perceptual brightness in the middle point of the two source colors).
float nrm_color_luminance = Luminance(nrm_color.rgb, true);
if (nrm_color_luminance != 0.0)
{
nrm_color.rgb *= pow(target_nrm_color_luminance, luminance_gamma) / nrm_color_luminance;
}
// Return the normalized average color.
return nrm_color;
}
/***** Main Functions *****/
// Returns an accurate (gamma corrected) sample of a gamma space space texture.
// Outputs in linear space for simplicity.
float4 LinearGammaCorrectedSample(float gamma)
{
float3 uvw = v_tex0;
float4 color = float4(0, 0, 0, 1);
if (resampling_method <= 1) // Bilinear
{
color = BilinearSample(uvw, gamma);
}
else if (resampling_method == 2) // Bicubic: B-Spline
{
color = BicubicSample(uvw, gamma, CUBIC_COEFF_GEN(1.0, 0.0));
}
else if (resampling_method == 3) // Bicubic: Mitchell-Netravali
{
color = BicubicSample(uvw, gamma, CUBIC_COEFF_GEN(1.0 / 3.0, 1.0 / 3.0));
}
else if (resampling_method == 4) // Bicubic: Catmull-Rom
{
color = BicubicSample(uvw, gamma, CUBIC_COEFF_GEN(0.0, 0.5));
}
else if (resampling_method == 5) // Sharp Bilinear
{
color = SharpBilinearSample(uvw, gamma);
}
else if (resampling_method == 6) // Area Sampling
{
color = AreaSampling(uvw, gamma);
}
else if (resampling_method == 7) // Nearest Neighbor
{
color = QuickSample(uvw, gamma);
}
else if (resampling_method == 8) // Bicubic: Hermite
{
color = BicubicSample(uvw, gamma, CUBIC_COEFF_GEN(0.0, 0.0));
}
return color;
}
void main()
{
// This tries to fall back on GPU HW sampling if it can (it won't be gamma corrected).
bool raw_resampling = resampling_method <= 0;
bool needs_rescaling = GetResolution() != GetWindowResolution();
bool needs_resampling = needs_rescaling && (OptionEnabled(hdr_output) || OptionEnabled(correct_gamma) || !raw_resampling);
float4 color;
if (needs_resampling)
{
// Doing linear sampling in "gamma space" on linear texture formats isn't correct.
// If the source and target resolutions don't match, the GPU will return a color
// that is the average of 4 gamma space colors, but gamma space colors can't be blended together,
// gamma needs to be de-applied first. This makes a big difference if colors change
// drastically between two pixels.
color = LinearGammaCorrectedSample(game_gamma);
}
else
{
// Default GPU HW sampling. Bilinear is identical to Nearest Neighbor if the input and output resolutions match.
if (needs_rescaling)
color = texture(samp0, v_tex0);
else
color = texture(samp1, v_tex0);
// Convert to linear before doing any other of follow up operations.
color.rgb = pow(color.rgb, float3(game_gamma));
}
if (OptionEnabled(correct_color_space))
{
if (game_color_space == 0)
color.rgb = color.rgb * from_NTSCM;
else if (game_color_space == 1)
color.rgb = color.rgb * from_NTSCJ;
else if (game_color_space == 2)
color.rgb = color.rgb * from_PAL;
}
if (OptionEnabled(hdr_output))
{
float hdr_paper_white = hdr_paper_white_nits / hdr_sdr_white_nits;
color.rgb *= hdr_paper_white;
}
if (OptionEnabled(linear_space_output))
{
// Nothing to do here
}
// Correct the SDR gamma for sRGB (PC/Monitor) or ~2.2 (Common TV gamma)
else if (OptionEnabled(correct_gamma))
{
if (OptionEnabled(sdr_display_gamma_sRGB))
color.rgb = LinearTosRGBGamma(color.rgb);
else
color.rgb = pow(color.rgb, float3(1.0 / sdr_display_custom_gamma));
}
// Restore the original gamma without changes
else
{
color.rgb = pow(color.rgb, float3(1.0 / game_gamma));
}
SetOutput(color);
}
-17
View File
@@ -1,17 +0,0 @@
void main()
{
float4 c0 = Sample();
float4 c1 = SampleOffset(int2(5, 5));
float y = (0.222 * c1.r) + (0.707 * c1.g) + (0.071 * c1.b);
float y2 = ((0.222 * c0.r) + (0.707 * c0.g) + (0.071 * c0.b)) / 3.0;
float red = c0.r;
float green = c0.g;
float blue = c0.b;
float alpha = c0.a;
red = y2 + (1.0 - y);
green = y2 + (1.0 - y);
blue = y2 + (1.0 - y);
SetOutput(float4(red, green, blue, alpha));
}
-29
View File
@@ -1,29 +0,0 @@
void main()
{
float4 c0 = Sample();
float red = 0.0;
float green = 0.0;
float blue = 0.0;
red = c0.r;
if (c0.r > 0.0 && c0.g > c0.r)
green = (c0.g - (c0.g - c0.r)) / 3.0;
if (c0.b > 0.0 && c0.r < 0.25)
{
red = c0.b;
green = c0.b / 3.0;
}
if (c0.g > 0.0 && c0.r < 0.25)
{
red = c0.g;
green = c0.g / 3.0;
}
if (((c0.r + c0.g + c0.b) / 3.0) > 0.9)
green = c0.r / 3.0;
SetOutput(float4(red, green, blue, 1.0));
}
-13
View File
@@ -1,13 +0,0 @@
void main()
{
float4 c0 = Sample();
float red = 0.0;
float green = 0.0;
float blue = 0.0;
float avg = (c0.r + c0.g + c0.b) / 3.0;
red = c0.r + (c0.g / 2.0) + (c0.b / 3.0);
green = c0.r / 3.0;
SetOutput(float4(red, green, blue, 1.0));
}
-15
View File
@@ -1,15 +0,0 @@
void main()
{
float4 c0 = Sample();
float4 c1 = SampleOffset(int2( 1, 1));
float4 c2 = SampleOffset(int2(-1, -1));
float red = c0.r;
float green = c0.g;
float blue = c0.b;
float alpha = c0.a;
red = c0.r - c1.b;
blue = c0.b - c2.r + (c0.g - c0.r);
SetOutput(float4(red, 0.0, blue, alpha));
}
-6
View File
@@ -1,6 +0,0 @@
void main()
{
float4 c0 = Sample();
float avg = (c0.r + c0.g + c0.b) / 3.0;
SetOutput(float4(avg, avg, avg, c0.a));
}
-7
View File
@@ -1,7 +0,0 @@
void main()
{
float4 c0 = Sample();
// Info: https://web.archive.org/web/20040101053504/http://www.oreillynet.com:80/cs/user/view/cs_msg/8691
float avg = (0.222 * c0.r) + (0.707 * c0.g) + (0.071 * c0.b);
SetOutput(float4(avg, avg, avg, c0.a));
}
-182
View File
@@ -1,182 +0,0 @@
// Set aspect ratio to 'stretch'
// Integer Scaling shader by One_More_Try / TryTwo
// Uses Sharp Bilinear from
// https://github.com/libretro/slang-shaders/blob/master/interpolation/shaders/sharp-bilinear.slang
// by Themaister, Public Domain license
/*
[configuration]
[OptionBool]
GUIName = Please set aspect ratio to stretch
OptionName = ASPECT_MSG
DefaultValue = false
[OptionBool]
GUIName = Use non-integer width
OptionName = WIDTH_UNLOCK
DefaultValue = false
[OptionBool]
GUIName = Stretch width to window
OptionName = WIDTH_SKIP
DependentOption = WIDTH_UNLOCK
DefaultValue = false
[OptionBool]
GUIName = Scale width to fit 4:3
OptionName = WIDTH_43
DependentOption = WIDTH_UNLOCK
DefaultValue = false
[OptionBool]
GUIName = Scale width to fit 16:9
OptionName = WIDTH_169
DependentOption = WIDTH_UNLOCK
DefaultValue = false
[OptionBool]
GUIName = Apply sharp bilinear for custom widths
OptionName = SHARP_BILINEAR
DefaultValue = false
[OptionRangeFloat]
GUIName = Sharp bilinear factor (0 = auto)
OptionName = SHARP_PRESCALE
MinValue = 0.0
MaxValue = 16.0
StepAmount = 1.0
DefaultValue = 0.0
[OptionBool]
GUIName = Manual scale - Set IR first
OptionName = MANUALSCALE
DefaultValue = false
[OptionRangeFloat]
GUIName = Integer scale - No higher than IR
OptionName = INTEGER_SCALE
DependentOption = MANUALSCALE
MaxValue = 5.0
MinValue = 1.0
DefaultValue = 1.0
StepAmount = 1.0
[OptionRangeFloat]
GUIName = Scale width
OptionName = WIDTH_SCALE
DependentOption = MANUALSCALE
MaxValue = 5.0
MinValue = -2.0
DefaultValue = 0.0
StepAmount = 1.0
[OptionBool]
GUIName = Auto downscaling
OptionName = DOWNSCALE
DefaultValue = false
[/configuration]
*/
void main()
{
float4 c0 = float4(0.0, 0.0, 0.0, 0.0);
float2 scale = float2(1.0, 1.0);
float2 xfb_res = GetResolution();
float2 win_res = GetWindowResolution();
float2 coords = GetCoordinates();
// ratio is used to rescale the coords to the xfb size, which allows for integer scaling.
// ratio can then be multiplied by an integer to upscale/downscale, but upscale isn't supported by
// point-sampling.
float2 ratio = win_res / xfb_res;
if (OptionEnabled(WIDTH_UNLOCK))
{
if (OptionEnabled(WIDTH_SKIP))
ratio.x = 1.0;
else if (OptionEnabled(WIDTH_43))
ratio.x = win_res.x / (xfb_res.y * 4 / 3);
else if (OptionEnabled(WIDTH_169))
ratio.x = win_res.x / (xfb_res.y * 16 / 9);
}
if (OptionEnabled(MANUALSCALE))
{
// There's no IR variable, so this guesses the IR, but may be off for some games.
float calc_ir = ceil(xfb_res.y / 500);
scale.y = calc_ir / GetOption(INTEGER_SCALE);
float manual_width = GetOption(WIDTH_SCALE);
if (manual_width < 0.0)
scale.x = scale.y * (abs(manual_width) + 1);
else
scale.x = scale.y / (manual_width + 1);
ratio = ratio * scale;
}
else if (OptionEnabled(DOWNSCALE) && (ratio.x < 1 || ratio.y < 1))
{
scale.x = ceil(max(1.0 / ratio.y, 1.0 / ratio.x));
scale.y = scale.x;
ratio = ratio * scale;
}
// y and x are used to determine black bars vs drawable space.
float y = win_res.y - xfb_res.y / scale.y;
float y_top = (y / 2.0) * GetInvWindowResolution().y;
float y_bottom = (win_res.y - y / 2.0) * GetInvWindowResolution().y;
float yloc = (coords.y - y_top) * ratio.y;
float x = win_res.x - xfb_res.x / scale.x;
if (OptionEnabled(WIDTH_UNLOCK))
{
if (OptionEnabled(WIDTH_SKIP))
x = 0.0;
else if (OptionEnabled(WIDTH_43))
x = win_res.x - xfb_res.y / scale.y * 4 / 3;
else if (OptionEnabled(WIDTH_169))
x = win_res.x - xfb_res.y / scale.y * 16 / 9;
}
float x_left = (x / 2.0) * GetInvWindowResolution().x;
float x_right = (win_res.x - x / 2.0) * GetInvWindowResolution().x;
float xloc = (coords.x - x_left) * ratio.x;
if (OptionEnabled(SHARP_BILINEAR) &&
(OptionEnabled(WIDTH_SKIP) || OptionEnabled(WIDTH_43) || OptionEnabled(WIDTH_169) ||
(OptionEnabled(MANUALSCALE) && GetOption(WIDTH_SCALE) != 0.0)))
{
float texel = xloc * xfb_res.x;
float texel_floored = floor(texel);
float s = frac(texel);
float scale_sharp = GetOption(SHARP_PRESCALE);
if (scale_sharp == 0)
{
if (OptionEnabled(WIDTH_43))
scale_sharp = (4 / 3 * xfb_res.y / xfb_res.x);
else if (OptionEnabled(WIDTH_169))
scale_sharp = (16 / 9 * xfb_res.y / xfb_res.x);
else
scale_sharp = ceil(win_res.x / xfb_res.x);
}
float region_range = 0.5 - 0.5 / scale_sharp;
float center_dist = s - 0.5;
float f = (center_dist - clamp(center_dist, -region_range, region_range)) * scale_sharp + 0.5;
float mod_texel = texel_floored + f;
xloc = mod_texel / xfb_res.x;
}
if (coords.x >= x_left && x_right >= coords.x && coords.y >= y_top && y_bottom >= coords.y)
{
float2 sample_loc = float2(xloc, yloc);
c0 = SampleLocation(sample_loc);
}
SetOutput(c0);
}
-4
View File
@@ -1,4 +0,0 @@
void main()
{
SetOutput(float4(1.0, 1.0, 1.0, 1.0) - Sample());
}
-4
View File
@@ -1,4 +0,0 @@
void main()
{
SetOutput(float4(0.0, 0.0, 0.7, 1.0) - Sample());
}
-7
View File
@@ -1,7 +0,0 @@
void main()
{
float4 c0 = Sample();
float4 c1 = SampleOffset(int2(5, 5));
SetOutput(c0 - c1);
}
-93
View File
@@ -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 (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));
}
}
-19
View File
@@ -1,19 +0,0 @@
void main()
{
float4 emboss = (SampleLocation(GetCoordinates()+GetInvResolution()) - SampleLocation(GetCoordinates()-GetInvResolution()))*2.0;
emboss -= (SampleLocation(GetCoordinates()+float2(1,-1)*GetInvResolution()).rgba - SampleLocation(GetCoordinates()+float2(-1,1)*GetInvResolution()).rgba);
float4 color = Sample();
if (color.r > 0.8 && color.b + color.b < 0.2)
{
SetOutput(float4(1,0,0,0));
}
else
{
color += emboss;
if (dot(color.rgb, float3(0.3, 0.5, 0.2)) > 0.5)
SetOutput(float4(1,1,1,1));
else
SetOutput(float4(0,0,0,0));
}
}
-10
View File
@@ -1,10 +0,0 @@
void main()
{
float4 c0 = Sample();
float green = c0.g;
if (c0.g < 0.50)
green = c0.r + c0.b;
SetOutput(float4(0.0, green, 0.0, 1.0));
}
-62
View File
@@ -1,62 +0,0 @@
void main()
{
//variables
float internalresolution = 1278.0;
float4 c0 = Sample();
//blur
float4 blurtotal = float4(0.0, 0.0, 0.0, 0.0);
float blursize = 1.5;
blurtotal += SampleLocation(GetCoordinates() + float2(-blursize, -blursize) * GetInvResolution());
blurtotal += SampleLocation(GetCoordinates() + float2(-blursize, blursize) * GetInvResolution());
blurtotal += SampleLocation(GetCoordinates() + float2( blursize, -blursize) * GetInvResolution());
blurtotal += SampleLocation(GetCoordinates() + float2( blursize, blursize) * GetInvResolution());
blurtotal += SampleLocation(GetCoordinates() + float2(-blursize, 0.0) * GetInvResolution());
blurtotal += SampleLocation(GetCoordinates() + float2( blursize, 0.0) * GetInvResolution());
blurtotal += SampleLocation(GetCoordinates() + float2( 0.0, -blursize) * GetInvResolution());
blurtotal += SampleLocation(GetCoordinates() + float2( 0.0, blursize) * GetInvResolution());
blurtotal *= 0.125;
c0 = blurtotal;
//greyscale
float grey = ((0.3 * c0.r) + (0.4 * c0.g) + (0.3 * c0.b));
// brighten
grey = grey * 0.5 + 0.7;
// darken edges
float x = GetCoordinates().x * GetResolution().x;
float y = GetCoordinates().y * GetResolution().y;
if (x > internalresolution/2.0)
x = internalresolution-x;
if (y > internalresolution/2.0)
y = internalresolution-y;
if (x > internalresolution/2.0*0.95)
x = internalresolution/2.0*0.95;
if (y > internalresolution/2.0*0.95)
y = internalresolution/2.0*0.95;
x = -x+641.0;
y = -y+641.0;
/*****inline square root routines*****/
// bit of a performance bottleneck.
// necessary to make the darkened area rounded
// instead of rhombus-shaped.
float sqrt = x / 10.0;
while ((sqrt*sqrt) < x)
sqrt+=0.1;
x = sqrt;
sqrt = y / 10.0;
while ((sqrt*sqrt) < y)
sqrt+=0.1;
y = sqrt;
x *= 2.0;
y *= 2.0;
grey -= x / 200.0;
grey -= y / 200.0;
// output
SetOutput(float4(0.0, grey, 0.0, 1.0));
}
@@ -1,71 +0,0 @@
void main()
{
//variables
float internalresolution = 1278.0;
float4 c0 = Sample();
//blur
float4 blurtotal = float4(0.0, 0.0, 0.0, 0.0);
float blursize = 1.5;
blurtotal += SampleLocation(GetCoordinates() + float2(-blursize, -blursize)*GetInvResolution());
blurtotal += SampleLocation(GetCoordinates() + float2(-blursize, blursize)*GetInvResolution());
blurtotal += SampleLocation(GetCoordinates() + float2( blursize, -blursize)*GetInvResolution());
blurtotal += SampleLocation(GetCoordinates() + float2( blursize, blursize)*GetInvResolution());
blurtotal += SampleLocation(GetCoordinates() + float2(-blursize, 0.0)*GetInvResolution());
blurtotal += SampleLocation(GetCoordinates() + float2( blursize, 0.0)*GetInvResolution());
blurtotal += SampleLocation(GetCoordinates() + float2( 0.0, -blursize)*GetInvResolution());
blurtotal += SampleLocation(GetCoordinates() + float2( 0.0, blursize)*GetInvResolution());
blurtotal *= 0.125;
c0 = blurtotal;
//greyscale
float grey = ((0.3 * c0.r) + (0.4 * c0.g) + (0.3 * c0.b));
// brighten and apply horizontal scanlines
// This would have been much simpler if I could get the stupid modulo (%) to work
// If anyone who is more well versed in Cg knows how to do this it'd be slightly more efficient
// float lineIntensity = ((GetCoordinates()[1] % 9) - 4) / 40;
float vPos = GetCoordinates().y*GetResolution().y / 9.0;
float lineIntensity = (((vPos - floor(vPos)) * 9.0) - 4.0) / 40.0;
grey = grey * 0.5 + 0.7 + lineIntensity;
// darken edges
float x = GetCoordinates().x * GetResolution().x;
float y = GetCoordinates().y * GetResolution().y;
if (x > internalresolution/2.0)
x = internalresolution-x;
if (y > internalresolution/2.0)
y = internalresolution-y;
if (x > internalresolution/2.0*0.95)
x = internalresolution/2.0*0.95;
if (y > internalresolution/2.0*0.95)
y = internalresolution/2.0*0.95;
x = -x + 641.0;
y = -y + 641.0;
//****inline square root routines*****/
// bit of a performance bottleneck.
// necessary to make the darkened area rounded
// instead of rhombus-shaped.
float sqrt = x / 10.0;
while ((sqrt*sqrt) < x)
sqrt+=0.1;
x = sqrt;
sqrt = y / 10.0;
while ((sqrt*sqrt) < y)
sqrt+=0.1;
y = sqrt;
x *= 2.0;
y *= 2.0;
grey -= x / 200.0;
grey -= y / 200.0;
// output
SetOutput(float4(0.0, grey, 0.0, 1.0));
}
-18
View File
@@ -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));
}
-18
View File
@@ -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));
}
-77
View File
@@ -1,77 +0,0 @@
void main()
{
float4 c0 = Sample();
float red = c0.r;
float blue = c0.b;
float green = c0.g;
float factor = 2.0;
float max = 0.8;
float min = 0.3;
if (c0.r > c0.g && c0.b > c0.g)
{
if (c0.r < c0.b + 0.05 && c0.b < c0.r + 0.05)
{
red = 0.7;
blue = 0.7;
green = 0.05;
}
else if (c0.r > c0.b + 0.05)
{
red = 0.7;
blue = 0.05;
green = 0.05;
}
else if (c0.b > c0.r + 0.05)
{
red = 0.05;
blue = 0.7;
green = 0.05;
}
}
if (c0.r > c0.b && c0.g > c0.b)
{
if (c0.r < c0.g + 0.05 && c0.g < c0.r + 0.05)
{
red = 0.7;
blue = 0.05;
green = 0.7;
}
else if (c0.r > c0.g + 0.05)
{
red = 0.7;
blue = 0.05;
green = 0.05;
}
else if (c0.g > c0.r + 0.05)
{
red = 0.05;
blue = 0.05;
green = 0.7;
}
}
if (c0.g > c0.r && c0.b > c0.r)
{
if (c0.g < c0.b + 0.05 && c0.b < c0.g + 0.05)
{
red = 0.05;
blue = 0.7;
green = 0.7;
}
else if (c0.g > c0.b + 0.05)
{
red = 0.05;
blue = 0.05;
green = 0.7;
}
else if (c0.b > c0.g + 0.05)
{
red = 0.05;
blue = 0.7;
green = 0.05;
}
}
SetOutput(float4(red, green, blue, c0.a));
}
-13
View File
@@ -1,13 +0,0 @@
void main()
{
float4 c0 = Sample();
// Same coefficients as grayscale2 at this point
float avg = (0.222 * c0.r) + (0.707 * c0.g) + (0.071 * c0.b);
float red=avg;
// Not sure about these coefficients, they just seem to produce the proper yellow
float green=avg*.75;
float blue=avg*.5;
SetOutput(float4(red, green, blue, c0.a));
}
-17
View File
@@ -1,17 +0,0 @@
void main()
{
float4 c0 = Sample();
float4 tmp = float4(0.0, 0.0, 0.0, 0.0);
tmp += c0 - SampleOffset(int2( 2, 2));
tmp += c0 - SampleOffset(int2(-2, -2));
tmp += c0 - SampleOffset(int2( 2, -2));
tmp += c0 - SampleOffset(int2(-2, 2));
float grey = ((0.222 * tmp.r) + (0.707 * tmp.g) + (0.071 * tmp.b));
// get rid of the bottom line, as it is incorrect.
if (GetCoordinates().y*GetResolution().y < 163.0)
tmp = float4(1.0, 1.0, 1.0, 1.0);
c0 = c0 + 1.0 - grey * 7.0;
SetOutput(float4(c0.r, c0.g, c0.b, 1.0));
}
-18
View File
@@ -1,18 +0,0 @@
void main()
{
float4 c0 = Sample();
float red = 0.0;
float blue = 0.0;
if (c0.r < 0.35 || c0.b > 0.5)
{
red = c0.g + c0.b;
}
else
{
red = c0.g + c0.b;
blue = c0.r + c0.b;
}
SetOutput(float4(red, 0.0, blue, 1.0));
}
-20
View File
@@ -1,20 +0,0 @@
void main()
{
float4 c0 = Sample();
float red = 0.0;
float green = 0.0;
float blue = 0.0;
if (c0.r < 0.35 || c0.b > 0.5)
{
red = c0.g + c0.b;
}
else
{
red = c0.g + c0.b;
blue = c0.r + c0.b;
green = c0.r + c0.b;
}
SetOutput(float4(red, green, blue, 1.0));
}
-5
View File
@@ -1,5 +0,0 @@
void main()
{
float4 c0 = Sample();
SetOutput(float4(c0.r * 1.5, c0.g, c0.b * 0.5, c0.a));
}
-4
View File
@@ -1,4 +0,0 @@
void main()
{
SetOutput(Sample().bgra);
}
-4
View File
@@ -1,4 +0,0 @@
void main()
{
SetOutput(Sample().brga);
}
-4
View File
@@ -1,4 +0,0 @@
void main()
{
SetOutput(Sample().gbra);
}
-4
View File
@@ -1,4 +0,0 @@
void main()
{
SetOutput(Sample().grba);
}
-4
View File
@@ -1,4 +0,0 @@
void main()
{
SetOutput(Sample().rbga);
}
-20
View File
@@ -1,20 +0,0 @@
void main()
{
float4 c0 = Sample();
float red = 0.0;
float green = 0.0;
float blue = 0.0;
if (c0.r < 0.3 || c0.b > 0.5)
{
blue = c0.r + c0.b;
red = c0.g + c0.b / 2.0;
}
else
{
red = c0.g + c0.b;
green = c0.r + c0.b;
}
SetOutput(float4(red, green, blue, 1.0));
}
-12
View File
@@ -1219,12 +1219,8 @@ DOLPHIN_SOURCES_CXX += \
$(CORE_DIR)/Source/Core/VideoCommon/Assets/CustomAssetLoader.cpp \
$(CORE_DIR)/Source/Core/VideoCommon/Assets/CustomTextureData.cpp \
$(CORE_DIR)/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.cpp \
$(CORE_DIR)/Source/Core/VideoCommon/Assets/MaterialAsset.cpp \
$(CORE_DIR)/Source/Core/VideoCommon/Assets/MeshAsset.cpp \
$(CORE_DIR)/Source/Core/VideoCommon/Assets/ShaderAsset.cpp \
$(CORE_DIR)/Source/Core/VideoCommon/Assets/TextureAsset.cpp \
$(CORE_DIR)/Source/Core/VideoCommon/Assets/TextureAssetUtils.cpp \
$(CORE_DIR)/Source/Core/VideoCommon/Assets/TextureSamplerValue.cpp \
$(CORE_DIR)/Source/Core/VideoCommon/AsyncRequests.cpp \
$(CORE_DIR)/Source/Core/VideoCommon/AsyncShaderCompiler.cpp \
$(CORE_DIR)/Source/Core/VideoCommon/BoundingBox.cpp \
@@ -1249,13 +1245,10 @@ DOLPHIN_SOURCES_CXX += \
$(CORE_DIR)/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.cpp \
$(CORE_DIR)/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTarget.cpp \
$(CORE_DIR)/Source/Core/VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.cpp \
$(CORE_DIR)/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.cpp \
$(CORE_DIR)/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/MoveAction.cpp \
$(CORE_DIR)/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/PrintAction.cpp \
$(CORE_DIR)/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/ScaleAction.cpp \
$(CORE_DIR)/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/SkipAction.cpp \
$(CORE_DIR)/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomPipeline.cpp \
$(CORE_DIR)/Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomShaderCache.cpp \
$(CORE_DIR)/Source/Core/VideoCommon/GraphicsModSystem/Runtime/FBInfo.cpp \
$(CORE_DIR)/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.cpp \
$(CORE_DIR)/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.cpp \
@@ -1276,13 +1269,8 @@ DOLPHIN_SOURCES_CXX += \
$(CORE_DIR)/Source/Core/VideoCommon/Present.cpp \
$(CORE_DIR)/Source/Core/VideoCommon/RenderState.cpp \
$(CORE_DIR)/Source/Core/VideoCommon/Resources/CustomResourceManager.cpp \
$(CORE_DIR)/Source/Core/VideoCommon/Resources/InvalidTextures.cpp \
$(CORE_DIR)/Source/Core/VideoCommon/Resources/MaterialResource.cpp \
$(CORE_DIR)/Source/Core/VideoCommon/Resources/Resource.cpp \
$(CORE_DIR)/Source/Core/VideoCommon/Resources/ShaderResource.cpp \
$(CORE_DIR)/Source/Core/VideoCommon/Resources/TextureAndSamplerResource.cpp \
$(CORE_DIR)/Source/Core/VideoCommon/Resources/TextureDataResource.cpp \
$(CORE_DIR)/Source/Core/VideoCommon/Resources/TexturePool.cpp \
$(CORE_DIR)/Source/Core/VideoCommon/ShaderCache.cpp \
$(CORE_DIR)/Source/Core/VideoCommon/ShaderCompileUtils.cpp \
$(CORE_DIR)/Source/Core/VideoCommon/ShaderGenCommon.cpp \
-3
View File
@@ -72,15 +72,12 @@
#define DUMP_DEBUG_BRANCHWATCH_DIR "BranchWatch"
#define DUMP_DEBUG_JITBLOCKS_DIR "JitBlocks"
#define LOGS_DIR "Logs"
#define SHADERS_DIR "Shaders"
#define WII_SYSCONF_DIR "shared2" DIR_SEP "sys"
#define WII_WC24CONF_DIR "shared2" DIR_SEP "wc24"
#define RESOURCES_DIR "Resources"
#define THEMES_DIR "Themes"
#define STYLES_DIR "Styles"
#define GBASAVES_DIR "Saves"
#define ANAGLYPH_DIR "Anaglyph"
#define PASSIVE_DIR "Passive"
#define PIPES_DIR "Pipes"
#define MEMORYWATCHER_DIR "MemoryWatcher"
#define WFSROOT_DIR "WFS"
-1
View File
@@ -962,7 +962,6 @@ static void RebuildUserDirectories(unsigned int dir_index)
s_user_paths[D_SHADERCACHE_IDX] = s_user_paths[D_CACHE_IDX] + SHADERCACHE_DIR DIR_SEP;
s_user_paths[D_RETROACHIEVEMENTSCACHE_IDX] =
s_user_paths[D_CACHE_IDX] + RETROACHIEVEMENTSCACHE_DIR DIR_SEP;
s_user_paths[D_SHADERS_IDX] = s_user_paths[D_USER_IDX] + SHADERS_DIR DIR_SEP;
s_user_paths[D_STATESAVES_IDX] = s_user_paths[D_USER_IDX] + STATESAVES_DIR DIR_SEP;
s_user_paths[D_SCREENSHOTS_IDX] = s_user_paths[D_USER_IDX] + SCREENSHOTS_DIR DIR_SEP;
s_user_paths[D_LOAD_IDX] = s_user_paths[D_USER_IDX] + LOAD_DIR DIR_SEP;
-1
View File
@@ -40,7 +40,6 @@ enum
D_REDUMPCACHE_IDX,
D_SHADERCACHE_IDX,
D_RETROACHIEVEMENTSCACHE_IDX,
D_SHADERS_IDX,
D_STATESAVES_IDX,
D_SCREENSHOTS_IDX,
D_HIRESTEXTURES_IDX,
@@ -143,8 +143,6 @@ const Info<AnisotropicFilteringMode> GFX_ENHANCE_MAX_ANISOTROPY{
{System::GFX, "Enhancements", "MaxAnisotropy"}, AnisotropicFilteringMode::Default};
const Info<OutputResamplingMode> GFX_ENHANCE_OUTPUT_RESAMPLING{
{System::GFX, "Enhancements", "OutputResampling"}, OutputResamplingMode::Default};
const Info<std::string> GFX_ENHANCE_POST_SHADER{
{System::GFX, "Enhancements", "PostProcessingShader"}, ""};
const Info<bool> GFX_ENHANCE_FORCE_TRUE_COLOR{{System::GFX, "Enhancements", "ForceTrueColor"},
true};
const Info<bool> GFX_ENHANCE_DISABLE_COPY_FILTER{{System::GFX, "Enhancements", "DisableCopyFilter"},
@@ -119,7 +119,6 @@ extern const Info<TextureFilteringMode> GFX_ENHANCE_FORCE_TEXTURE_FILTERING;
// NOTE - this is x in (1 << x)
extern const Info<AnisotropicFilteringMode> GFX_ENHANCE_MAX_ANISOTROPY;
extern const Info<OutputResamplingMode> GFX_ENHANCE_OUTPUT_RESAMPLING;
extern const Info<std::string> GFX_ENHANCE_POST_SHADER;
extern const Info<bool> GFX_ENHANCE_FORCE_TRUE_COLOR;
extern const Info<bool> GFX_ENHANCE_DISABLE_COPY_FILTER;
extern const Info<bool> GFX_ENHANCE_ARBITRARY_MIPMAP_DETECTION;
-2
View File
@@ -283,8 +283,6 @@ void CreateDirectories()
File::CreateFullPath(File::GetUserPath(D_LOGS_IDX));
File::CreateFullPath(File::GetUserPath(D_MAPS_IDX));
File::CreateFullPath(File::GetUserPath(D_SCREENSHOTS_IDX));
File::CreateFullPath(File::GetUserPath(D_SHADERS_IDX));
File::CreateFullPath(File::GetUserPath(D_SHADERS_IDX) + ANAGLYPH_DIR DIR_SEP);
File::CreateFullPath(File::GetUserPath(D_RETROACHIEVEMENTSCACHE_IDX));
File::CreateFullPath(File::GetUserPath(D_STATESAVES_IDX));
File::CreateFullPath(File::GetUserPath(D_ASM_ROOT_IDX));
-1
View File
@@ -29,7 +29,6 @@
#include "VideoCommon/BPFunctions.h"
#include "VideoCommon/FramebufferManager.h"
#include "VideoCommon/PostProcessing.h"
#include "VideoCommon/Present.h"
#include "VideoCommon/RenderState.h"
#include "VideoCommon/VideoConfig.h"
@@ -84,7 +84,6 @@ void VideoBackend::FillBackendInfo()
g_backend_info.bSupportsGeometryShaders = true;
g_backend_info.bSupportsComputeShaders = false;
g_backend_info.bSupports3DVision = true;
g_backend_info.bSupportsPostProcessing = true;
g_backend_info.bSupportsPaletteConversion = true;
g_backend_info.bSupportsClipControl = true;
g_backend_info.bSupportsDepthClamp = true;
@@ -59,7 +59,6 @@ void StateManager::Apply()
const bool dirtyConstants = m_dirtyFlags.test(DirtyFlag_PixelConstants) ||
m_dirtyFlags.test(DirtyFlag_VertexConstants) ||
m_dirtyFlags.test(DirtyFlag_CustomConstants) ||
m_dirtyFlags.test(DirtyFlag_GeometryConstants);
const bool dirtyShaders = m_dirtyFlags.test(DirtyFlag_PixelShader) ||
m_dirtyFlags.test(DirtyFlag_VertexShader) ||
@@ -85,13 +84,6 @@ void StateManager::Apply()
m_current.vertexConstants = m_pending.vertexConstants;
}
if (m_current.customConstants != m_pending.customConstants)
{
D3D::context->PSSetConstantBuffers(2, 1, &m_pending.customConstants);
D3D::context->VSSetConstantBuffers(2, 1, &m_pending.customConstants);
m_current.customConstants = m_pending.customConstants;
}
if (m_current.geometryConstants != m_pending.geometryConstants)
{
D3D::context->GSSetConstantBuffers(0, 1, &m_pending.geometryConstants);
@@ -332,8 +324,6 @@ void StateManager::Restore()
D3D::context->PSSetConstantBuffers(0, 2, m_pending.pixelConstants.data());
D3D::context->VSSetConstantBuffers(0, 1, &m_pending.vertexConstants);
D3D::context->VSSetConstantBuffers(1, 1, &m_pending.vertexConstants);
D3D::context->PSSetConstantBuffers(2, 1, &m_pending.customConstants);
D3D::context->VSSetConstantBuffers(2, 1, &m_pending.customConstants);
D3D::context->GSSetConstantBuffers(0, 1, &m_pending.geometryConstants);
D3D::context->IASetVertexBuffers(0, 1, &m_pending.vertexBuffer, &m_pending.vertexBufferStride,
&m_pending.vertexBufferOffset);
-10
View File
@@ -108,14 +108,6 @@ public:
m_pending.vertexConstants = buffer;
}
void SetCustomConstants(ID3D11Buffer* buffer)
{
if (m_current.customConstants != buffer)
m_dirtyFlags.set(DirtyFlag_CustomConstants);
m_pending.customConstants = buffer;
}
void SetGeometryConstants(ID3D11Buffer* buffer)
{
if (m_current.geometryConstants != buffer)
@@ -240,7 +232,6 @@ private:
DirtyFlag_Sampler0 = DirtyFlag_Texture0 + VideoCommon::MAX_PIXEL_SHADER_SAMPLERS,
DirtyFlag_PixelConstants = DirtyFlag_Sampler0 + VideoCommon::MAX_PIXEL_SHADER_SAMPLERS,
DirtyFlag_VertexConstants,
DirtyFlag_CustomConstants, // Custom shader constants used by both vertex/pixel stages
DirtyFlag_GeometryConstants,
DirtyFlag_VertexBuffer,
@@ -266,7 +257,6 @@ private:
std::array<ID3D11SamplerState*, VideoCommon::MAX_PIXEL_SHADER_SAMPLERS> samplers;
std::array<ID3D11Buffer*, 2> pixelConstants;
ID3D11Buffer* vertexConstants;
ID3D11Buffer* customConstants;
ID3D11Buffer* geometryConstants;
ID3D11Buffer* vertexBuffer;
ID3D11Buffer* indexBuffer;
@@ -299,25 +299,10 @@ void VertexManager::UploadUniforms()
pixel_shader_manager.dirty = false;
}
if (pixel_shader_manager.custom_constants_dirty)
{
if (m_last_custom_buffer_size < pixel_shader_manager.custom_constants.size())
{
m_custom_constant_buffer =
AllocateConstantBuffer(static_cast<u32>(pixel_shader_manager.custom_constants.size()));
}
UpdateConstantBuffer(m_custom_constant_buffer.Get(),
pixel_shader_manager.custom_constants.data(),
static_cast<u32>(pixel_shader_manager.custom_constants.size()));
m_last_custom_buffer_size = pixel_shader_manager.custom_constants.size();
pixel_shader_manager.custom_constants_dirty = false;
}
D3D::stateman->SetPixelConstants(
m_pixel_constant_buffer.Get(),
g_ActiveConfig.bEnablePixelLighting ? m_vertex_constant_buffer.Get() : nullptr);
D3D::stateman->SetVertexConstants(m_vertex_constant_buffer.Get());
D3D::stateman->SetGeometryConstants(m_geometry_constant_buffer.Get());
D3D::stateman->SetCustomConstants(m_custom_constant_buffer.Get());
}
} // namespace DX11
@@ -68,8 +68,6 @@ private:
ComPtr<ID3D11Buffer> m_geometry_constant_buffer = nullptr;
ComPtr<ID3D11Buffer> m_pixel_constant_buffer = nullptr;
ComPtr<ID3D11Buffer> m_custom_constant_buffer = nullptr;
std::size_t m_last_custom_buffer_size = 0;
ComPtr<ID3D11Buffer> m_texel_buffer = nullptr;
std::array<ComPtr<ID3D11ShaderResourceView>, NUM_TEXEL_BUFFER_FORMATS> m_texel_buffer_views;
@@ -141,7 +141,6 @@ void VertexManager::UploadUniforms()
UpdateVertexShaderConstants();
UpdateGeometryShaderConstants();
UpdatePixelShaderConstants();
UpdateCustomShaderConstants();
}
void VertexManager::UpdateVertexShaderConstants()
@@ -195,36 +194,12 @@ void VertexManager::UpdatePixelShaderConstants()
}
}
void VertexManager::UpdateCustomShaderConstants()
{
auto& system = Core::System::GetInstance();
auto& pixel_shader_manager = system.GetPixelShaderManager();
if (!ReserveConstantStorage())
return;
if (pixel_shader_manager.custom_constants_dirty)
{
Gfx::GetInstance()->SetConstantBuffer(2, m_uniform_stream_buffer.GetCurrentGPUPointer());
std::memcpy(m_uniform_stream_buffer.GetCurrentHostPointer(),
pixel_shader_manager.custom_constants.data(),
pixel_shader_manager.custom_constants.size());
m_uniform_stream_buffer.CommitMemory(
static_cast<u32>(pixel_shader_manager.custom_constants.size()));
pixel_shader_manager.custom_constants_dirty = false;
}
}
bool VertexManager::ReserveConstantStorage()
{
auto& system = Core::System::GetInstance();
auto& pixel_shader_manager = system.GetPixelShaderManager();
static constexpr u32 reserve_size =
static_cast<u32>(std::max({sizeof(PixelShaderConstants), sizeof(VertexShaderConstants),
sizeof(GeometryShaderConstants)}));
const u32 custom_constants_size = static_cast<u32>(pixel_shader_manager.custom_constants.size());
if (m_uniform_stream_buffer.ReserveMemory(reserve_size + custom_constants_size,
if (m_uniform_stream_buffer.ReserveMemory(reserve_size,
D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT))
{
return true;
@@ -253,11 +228,7 @@ void VertexManager::UploadAllConstants()
constexpr u32 geometry_constants_offset =
Common::AlignUp(vertex_constants_offset + sizeof(VertexShaderConstants),
D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT);
constexpr u32 custom_pixel_constants_offset =
Common::AlignUp(geometry_constants_offset + sizeof(GeometryShaderConstants),
D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT);
const u32 allocation_size = custom_pixel_constants_offset +
static_cast<u32>(pixel_shader_manager.custom_constants.size());
constexpr u32 allocation_size = geometry_constants_offset + sizeof(GeometryShaderConstants);
// Allocate everything at once.
// We should only be here if the buffer was full and a command buffer was submitted anyway.
@@ -273,8 +244,6 @@ void VertexManager::UploadAllConstants()
pixel_constants_offset);
Gfx::GetInstance()->SetConstantBuffer(1, m_uniform_stream_buffer.GetCurrentGPUPointer() +
vertex_constants_offset);
Gfx::GetInstance()->SetConstantBuffer(2, m_uniform_stream_buffer.GetCurrentGPUPointer() +
custom_pixel_constants_offset);
Gfx::GetInstance()->SetConstantBuffer(3, m_uniform_stream_buffer.GetCurrentGPUPointer() +
geometry_constants_offset);
@@ -288,13 +257,6 @@ void VertexManager::UploadAllConstants()
&vertex_shader_manager.constants, sizeof(VertexShaderConstants));
std::memcpy(m_uniform_stream_buffer.GetCurrentHostPointer() + geometry_constants_offset,
&geometry_shader_manager.constants, sizeof(GeometryShaderConstants));
if (!pixel_shader_manager.custom_constants.empty())
{
std::memcpy(m_uniform_stream_buffer.GetCurrentHostPointer() + custom_pixel_constants_offset,
pixel_shader_manager.custom_constants.data(),
pixel_shader_manager.custom_constants.size());
}
// Finally, flush buffer memory after copying
m_uniform_stream_buffer.CommitMemory(allocation_size);
ADDSTAT(g_stats.this_frame.bytes_uniform_streamed, allocation_size);
@@ -35,7 +35,6 @@ protected:
void UpdateVertexShaderConstants();
void UpdateGeometryShaderConstants();
void UpdatePixelShaderConstants();
void UpdateCustomShaderConstants();
// Allocates storage in the uniform buffer of the specified size. If this storage cannot be
// allocated immediately, the current command buffer will be submitted and all stage's
@@ -59,7 +59,6 @@ void VideoBackend::FillBackendInfo()
g_backend_info.bSupportsBBox = true;
g_backend_info.bSupportsGSInstancing = true;
g_backend_info.bSupportsPaletteConversion = true;
g_backend_info.bSupportsPostProcessing = true;
g_backend_info.bSupportsClipControl = true;
g_backend_info.bSupportsSSAA = true;
g_backend_info.bSupportsFragmentStoresAndAtomics = true;
@@ -35,7 +35,6 @@ void VideoBackend::InitBackendInfo(const WindowSystemInfo& wsi)
g_backend_info.bSupportsBindingLayout = true;
g_backend_info.bSupportsBBox = true;
g_backend_info.bSupportsGSInstancing = true;
g_backend_info.bSupportsPostProcessing = false;
g_backend_info.bSupportsPaletteConversion = true;
g_backend_info.bSupportsClipControl = true;
g_backend_info.bSupportsSSAA = true;
@@ -119,7 +119,6 @@ bool VideoBackend::FillBackendInfo(GLContext* context)
g_backend_info.bSupportsGeometryShaders = true;
g_backend_info.bSupportsComputeShaders = false;
g_backend_info.bSupports3DVision = false;
g_backend_info.bSupportsPostProcessing = true;
g_backend_info.bSupportsSSAA = true;
g_backend_info.bSupportsReversedDepthRange = true;
g_backend_info.bSupportsLogicOp = true;
@@ -227,12 +227,9 @@ void ProgramShaderCache::UploadConstants()
auto& pixel_shader_manager = system.GetPixelShaderManager();
auto& vertex_shader_manager = system.GetVertexShaderManager();
auto& geometry_shader_manager = system.GetGeometryShaderManager();
if (pixel_shader_manager.dirty || vertex_shader_manager.dirty || geometry_shader_manager.dirty ||
pixel_shader_manager.custom_constants_dirty)
if (pixel_shader_manager.dirty || vertex_shader_manager.dirty || geometry_shader_manager.dirty)
{
const u32 custom_constants_size = static_cast<u32>(
Common::AlignUp(pixel_shader_manager.custom_constants.size(), s_ubo_align));
auto buffer = s_buffer->Map(s_ubo_buffer_size + custom_constants_size, s_ubo_align);
auto buffer = s_buffer->Map(s_ubo_buffer_size, s_ubo_align);
memcpy(buffer.first, &pixel_shader_manager.constants, sizeof(PixelShaderConstants));
@@ -241,17 +238,10 @@ void ProgramShaderCache::UploadConstants()
memcpy(buffer.first + size, &vertex_shader_manager.constants, sizeof(VertexShaderConstants));
size += Common::AlignUp(sizeof(VertexShaderConstants), s_ubo_align);
if (!pixel_shader_manager.custom_constants.empty())
{
memcpy(buffer.first + size, pixel_shader_manager.custom_constants.data(),
pixel_shader_manager.custom_constants.size());
size += custom_constants_size;
}
memcpy(buffer.first + size, &geometry_shader_manager.constants,
sizeof(GeometryShaderConstants));
s_buffer->Unmap(s_ubo_buffer_size + custom_constants_size);
s_buffer->Unmap(s_ubo_buffer_size);
glBindBufferRange(GL_UNIFORM_BUFFER, 1, s_buffer->m_buffer, buffer.second,
sizeof(PixelShaderConstants));
@@ -259,22 +249,14 @@ void ProgramShaderCache::UploadConstants()
glBindBufferRange(GL_UNIFORM_BUFFER, 2, s_buffer->m_buffer, buffer.second + size,
sizeof(VertexShaderConstants));
size += Common::AlignUp(sizeof(VertexShaderConstants), s_ubo_align);
if (!pixel_shader_manager.custom_constants.empty())
{
glBindBufferRange(GL_UNIFORM_BUFFER, 3, s_buffer->m_buffer, buffer.second + size,
pixel_shader_manager.custom_constants.size());
size += Common::AlignUp(pixel_shader_manager.custom_constants.size(), s_ubo_align);
}
glBindBufferRange(GL_UNIFORM_BUFFER, 4, s_buffer->m_buffer, buffer.second + size,
sizeof(GeometryShaderConstants));
pixel_shader_manager.dirty = false;
vertex_shader_manager.dirty = false;
geometry_shader_manager.dirty = false;
pixel_shader_manager.custom_constants_dirty = false;
ADDSTAT(g_stats.this_frame.bytes_uniform_streamed, s_ubo_buffer_size + custom_constants_size);
ADDSTAT(g_stats.this_frame.bytes_uniform_streamed, s_ubo_buffer_size);
}
}
@@ -254,28 +254,11 @@ void VertexManager::UpdatePixelShaderConstants()
pixel_shader_manager.dirty = false;
}
if (pixel_shader_manager.custom_constants_dirty)
{
StateTracker::GetInstance()->SetGXUniformBuffer(
UBO_DESCRIPTOR_SET_BINDING_CUST, m_uniform_stream_buffer->GetBuffer(),
m_uniform_stream_buffer->GetCurrentOffset(),
static_cast<u32>(pixel_shader_manager.custom_constants.size()));
std::memcpy(m_uniform_stream_buffer->GetCurrentHostPointer(),
pixel_shader_manager.custom_constants.data(),
pixel_shader_manager.custom_constants.size());
m_uniform_stream_buffer->CommitMemory(
static_cast<u32>(pixel_shader_manager.custom_constants.size()));
pixel_shader_manager.custom_constants_dirty = false;
}
}
bool VertexManager::ReserveConstantStorage()
{
auto& system = Core::System::GetInstance();
auto& pixel_shader_manager = system.GetPixelShaderManager();
const u32 custom_constants_size = static_cast<u32>(pixel_shader_manager.custom_constants.size());
if (m_uniform_stream_buffer->ReserveMemory(m_uniform_buffer_reserve_size + custom_constants_size,
if (m_uniform_stream_buffer->ReserveMemory(m_uniform_buffer_reserve_size,
g_vulkan_context->GetUniformBufferAlignment()))
{
return true;
@@ -296,8 +279,6 @@ void VertexManager::UploadAllConstants()
auto& system = Core::System::GetInstance();
auto& pixel_shader_manager = system.GetPixelShaderManager();
const u32 custom_constants_size = static_cast<u32>(pixel_shader_manager.custom_constants.size());
// We are free to re-use parts of the buffer now since we're uploading all constants.
const u32 ub_alignment = static_cast<u32>(g_vulkan_context->GetUniformBufferAlignment());
const u32 pixel_constants_offset = 0;
@@ -305,9 +286,8 @@ void VertexManager::UploadAllConstants()
Common::AlignUp(pixel_constants_offset + sizeof(PixelShaderConstants), ub_alignment);
const u32 geometry_constants_offset =
Common::AlignUp(vertex_constants_offset + sizeof(VertexShaderConstants), ub_alignment);
const u32 custom_pixel_constants_offset =
Common::AlignUp(geometry_constants_offset + sizeof(GeometryShaderConstants), ub_alignment);
const u32 allocation_size = custom_pixel_constants_offset + custom_constants_size;
const u32 allocation_size =
static_cast<u32>(geometry_constants_offset + sizeof(GeometryShaderConstants));
// Allocate everything at once.
// We should only be here if the buffer was full and a command buffer was submitted anyway.
@@ -330,13 +310,6 @@ void VertexManager::UploadAllConstants()
m_uniform_stream_buffer->GetCurrentOffset() + vertex_constants_offset,
sizeof(VertexShaderConstants));
if (!pixel_shader_manager.custom_constants.empty())
{
StateTracker::GetInstance()->SetGXUniformBuffer(
UBO_DESCRIPTOR_SET_BINDING_CUST, m_uniform_stream_buffer->GetBuffer(),
m_uniform_stream_buffer->GetCurrentOffset() + custom_pixel_constants_offset,
custom_constants_size);
}
StateTracker::GetInstance()->SetGXUniformBuffer(
UBO_DESCRIPTOR_SET_BINDING_GS, m_uniform_stream_buffer->GetBuffer(),
m_uniform_stream_buffer->GetCurrentOffset() + geometry_constants_offset,
@@ -349,12 +322,6 @@ void VertexManager::UploadAllConstants()
&vertex_shader_manager.constants, sizeof(VertexShaderConstants));
std::memcpy(m_uniform_stream_buffer->GetCurrentHostPointer() + geometry_constants_offset,
&geometry_shader_manager.constants, sizeof(GeometryShaderConstants));
if (!pixel_shader_manager.custom_constants.empty())
{
std::memcpy(m_uniform_stream_buffer->GetCurrentHostPointer() + custom_pixel_constants_offset,
pixel_shader_manager.custom_constants.data(),
pixel_shader_manager.custom_constants.size());
}
// Finally, flush buffer memory after copying
m_uniform_stream_buffer->CommitMemory(allocation_size);
@@ -435,7 +435,6 @@ void VulkanContext::PopulateBackendInfo(BackendInfo* backend_info)
backend_info->bSupportsShaderBinaries = true; // Assumed support.
backend_info->bSupportsPipelineCacheData = false; // Handled via pipeline caches.
backend_info->bSupportsDynamicSamplerIndexing = true; // Assumed support.
backend_info->bSupportsPostProcessing = true; // Assumed support.
backend_info->bSupportsBackgroundCompiling = true; // Assumed support.
backend_info->bSupportsCopyToVram = true; // Assumed support.
backend_info->bSupportsReversedDepthRange = true; // Assumed support.
@@ -8,10 +8,6 @@
namespace VideoCommon
{
class CustomTextureData;
struct MaterialData;
struct MeshData;
struct RasterSurfaceShaderData;
struct TextureAndSamplerData;
// This class provides functionality to load
// specific data (like textures). Where this data
@@ -29,20 +25,7 @@ public:
virtual ~CustomAssetLibrary() = default;
// Loads a texture with a sampler and type, if there are no levels, bytes loaded will be empty
virtual LoadInfo LoadTexture(const AssetID& asset_id, TextureAndSamplerData* data) = 0;
// Loads a texture, if there are no levels, bytes loaded will be empty
virtual LoadInfo LoadTexture(const AssetID& asset_id, CustomTextureData* data) = 0;
// Loads a raster surface shader
virtual LoadInfo LoadRasterSurfaceShader(const AssetID& asset_id,
RasterSurfaceShaderData* data) = 0;
// Loads a material
virtual LoadInfo LoadMaterial(const AssetID& asset_id, MaterialData* data) = 0;
// Loads a mesh
virtual LoadInfo LoadMesh(const AssetID& asset_id, MeshData* data) = 0;
};
} // namespace VideoCommon
@@ -14,9 +14,6 @@
#include "Common/Logging/Log.h"
#include "Common/StringUtil.h"
#include "Core/System.h"
#include "VideoCommon/Assets/MaterialAsset.h"
#include "VideoCommon/Assets/MeshAsset.h"
#include "VideoCommon/Assets/ShaderAsset.h"
#include "VideoCommon/Assets/TextureAsset.h"
#include "VideoCommon/Assets/TextureAssetUtils.h"
#include "VideoCommon/RenderState.h"
@@ -40,278 +37,6 @@ std::size_t GetAssetSize(const CustomTextureData& data)
}
} // namespace
CustomAssetLibrary::LoadInfo
DirectFilesystemAssetLibrary::LoadRasterSurfaceShader(const AssetID& asset_id,
RasterSurfaceShaderData* data)
{
const auto asset_map = GetAssetMapForID(asset_id);
// Asset map for a pixel shader is the shader and some metadata
if (asset_map.size() != 3)
{
ERROR_LOG_FMT(VIDEO, "Asset '{}' expected to have three files mapped!", asset_id);
return {};
}
const auto metadata = asset_map.find("metadata");
if (metadata == asset_map.end())
{
ERROR_LOG_FMT(VIDEO, "Asset '{}' expected to have a metadata entry mapped!", asset_id);
return {};
}
const auto vertex_shader = asset_map.find("vertex_shader");
if (vertex_shader == asset_map.end())
{
ERROR_LOG_FMT(VIDEO, "Asset '{}' expected to have a vertex shader entry mapped!", asset_id);
return {};
}
const auto pixel_shader = asset_map.find("pixel_shader");
if (pixel_shader == asset_map.end())
{
ERROR_LOG_FMT(VIDEO, "Asset '{}' expected to have a pixel shader entry mapped!", asset_id);
return {};
}
std::size_t metadata_size;
{
std::error_code ec;
metadata_size = std::filesystem::file_size(metadata->second, ec);
if (ec)
{
ERROR_LOG_FMT(VIDEO,
"Asset '{}' error - failed to get shader metadata file size with error '{}'!",
asset_id, ec);
return {};
}
}
std::size_t vertex_shader_size;
{
std::error_code ec;
vertex_shader_size = std::filesystem::file_size(vertex_shader->second, ec);
if (ec)
{
ERROR_LOG_FMT(
VIDEO, "Asset '{}' error - failed to get vertex shader source file size with error '{}'!",
asset_id, ec);
return {};
}
}
std::size_t pixel_shader_size;
{
std::error_code ec;
pixel_shader_size = std::filesystem::file_size(pixel_shader->second, ec);
if (ec)
{
ERROR_LOG_FMT(
VIDEO, "Asset '{}' error - failed to get pixel shader source file size with error '{}'!",
asset_id, ec);
return {};
}
}
const auto approx_mem_size = metadata_size + vertex_shader_size + pixel_shader_size;
if (!File::ReadFileToString(PathToString(vertex_shader->second), data->vertex_source))
{
ERROR_LOG_FMT(VIDEO, "Asset '{}' error - failed to load the vertex shader file '{}',",
asset_id, PathToString(vertex_shader->second));
return {};
}
if (!File::ReadFileToString(PathToString(pixel_shader->second), data->pixel_source))
{
ERROR_LOG_FMT(VIDEO, "Asset '{}' error - failed to load the pixel shader file '{}',", asset_id,
PathToString(pixel_shader->second));
return {};
}
picojson::value root;
std::string error;
if (!JsonFromFile(PathToString(metadata->second), &root, &error))
{
ERROR_LOG_FMT(VIDEO,
"Asset '{}' error - failed to load the json file '{}', due to parse error: {}",
asset_id, PathToString(metadata->second), error);
return {};
}
if (!root.is<picojson::object>())
{
ERROR_LOG_FMT(
VIDEO,
"Asset '{}' error - failed to load the json file '{}', due to root not being an object!",
asset_id, PathToString(metadata->second));
return {};
}
const auto& root_obj = root.get<picojson::object>();
if (!RasterSurfaceShaderData::FromJson(asset_id, root_obj, data))
return {};
const std::string graphics_mod_builtin =
File::GetSysDirectory() + GRAPHICSMOD_DIR + "/Builtin" + "/Shaders";
data->shader_includer = std::make_unique<VideoCommon::ShaderIncluder>(
PathToString(pixel_shader->second.parent_path()), graphics_mod_builtin);
return LoadInfo{approx_mem_size};
}
CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadMaterial(const AssetID& asset_id,
MaterialData* data)
{
const auto asset_map = GetAssetMapForID(asset_id);
// Material is expected to have one asset mapped
if (asset_map.empty() || asset_map.size() > 1)
{
ERROR_LOG_FMT(VIDEO, "Asset '{}' error - material expected to have one file mapped!", asset_id);
return {};
}
const auto& asset_path = asset_map.begin()->second;
std::size_t metadata_size;
{
std::error_code ec;
metadata_size = std::filesystem::file_size(asset_path, ec);
if (ec)
{
ERROR_LOG_FMT(VIDEO, "Asset '{}' error - failed to get material file size with error '{}'!",
asset_id, ec);
return {};
}
}
picojson::value root;
std::string error;
if (!JsonFromFile(PathToString(asset_path), &root, &error))
{
ERROR_LOG_FMT(
VIDEO,
"Asset '{}' error - material failed to load the json file '{}', due to parse error: {}",
asset_id, PathToString(asset_path), error);
return {};
}
if (!root.is<picojson::object>())
{
ERROR_LOG_FMT(VIDEO,
"Asset '{}' error - material failed to load the json file '{}', due to root not "
"being an object!",
asset_id, PathToString(asset_path));
return {};
}
const auto& root_obj = root.get<picojson::object>();
if (!MaterialData::FromJson(asset_id, root_obj, data))
{
ERROR_LOG_FMT(VIDEO,
"Asset '{}' error - material failed to load the json file '{}', as material "
"json could not be parsed!",
asset_id, PathToString(asset_path));
return {};
}
return LoadInfo{metadata_size};
}
CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadMesh(const AssetID& asset_id,
MeshData* data)
{
const auto asset_map = GetAssetMapForID(asset_id);
// Asset map for a mesh is the mesh and some metadata
if (asset_map.size() != 2)
{
ERROR_LOG_FMT(VIDEO, "Asset '{}' expected to have two files mapped!", asset_id);
return {};
}
const auto metadata = asset_map.find("metadata");
const auto mesh = asset_map.find("mesh");
if (metadata == asset_map.end())
{
ERROR_LOG_FMT(VIDEO, "Asset '{}' expected to have a metadata entry mapped!", asset_id);
return {};
}
if (mesh == asset_map.end())
{
ERROR_LOG_FMT(VIDEO, "Asset '{}' expected to have a mesh entry mapped!", asset_id);
return {};
}
std::size_t metadata_size;
{
std::error_code ec;
metadata_size = std::filesystem::file_size(metadata->second, ec);
if (ec)
{
ERROR_LOG_FMT(VIDEO,
"Asset '{}' error - failed to get mesh metadata file size with error '{}'!",
asset_id, ec);
return {};
}
}
std::size_t mesh_size;
{
std::error_code ec;
mesh_size = std::filesystem::file_size(mesh->second, ec);
if (ec)
{
ERROR_LOG_FMT(VIDEO, "Asset '{}' error - failed to get mesh file size with error '{}'!",
asset_id, ec);
return {};
}
}
const auto approx_mem_size = metadata_size + mesh_size;
File::IOFile file(PathToString(mesh->second), "rb");
if (!file.IsOpen())
{
ERROR_LOG_FMT(VIDEO, "Asset '{}' error - failed to open mesh file '{}'!", asset_id,
PathToString(mesh->second));
return {};
}
std::vector<u8> bytes;
bytes.reserve(file.GetSize());
file.ReadBytes(bytes.data(), file.GetSize());
if (!MeshData::FromDolphinMesh(bytes, data))
{
ERROR_LOG_FMT(VIDEO, "Asset '{}' error - failed to load the mesh file '{}'!", asset_id,
PathToString(mesh->second));
return {};
}
picojson::value root;
std::string error;
if (!JsonFromFile(PathToString(metadata->second), &root, &error))
{
ERROR_LOG_FMT(VIDEO,
"Asset '{}' error - failed to load the json file '{}', due to parse error: {}",
asset_id, PathToString(metadata->second), error);
return {};
}
if (!root.is<picojson::object>())
{
ERROR_LOG_FMT(
VIDEO,
"Asset '{}' error - failed to load the json file '{}', due to root not being an object!",
asset_id, PathToString(metadata->second));
return {};
}
const auto& root_obj = root.get<picojson::object>();
if (!MeshData::FromJson(asset_id, root_obj, data))
return {};
return LoadInfo{approx_mem_size};
}
CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadTexture(const AssetID& asset_id,
CustomTextureData* data)
{
@@ -342,79 +67,6 @@ CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadTexture(const Ass
return LoadInfo{GetAssetSize(*data)};
}
CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadTexture(const AssetID& asset_id,
TextureAndSamplerData* data)
{
const auto asset_map = GetAssetMapForID(asset_id);
// Texture can optionally have a metadata file as well
if (asset_map.empty() || asset_map.size() > 2)
{
ERROR_LOG_FMT(VIDEO, "Asset '{}' error - raw texture expected to have one or two files mapped!",
asset_id);
return {};
}
const auto metadata = asset_map.find("metadata");
const auto texture_path = asset_map.find("texture");
if (texture_path == asset_map.end())
{
ERROR_LOG_FMT(VIDEO, "Asset '{}' expected to have a texture entry mapped!", asset_id);
return {};
}
std::size_t metadata_size = 0;
if (metadata != asset_map.end())
{
std::error_code ec;
metadata_size = std::filesystem::file_size(metadata->second, ec);
if (ec)
{
ERROR_LOG_FMT(VIDEO,
"Asset '{}' error - failed to get texture metadata file size with error '{}'!",
asset_id, ec);
return {};
}
picojson::value root;
std::string error;
if (!JsonFromFile(PathToString(metadata->second), &root, &error))
{
ERROR_LOG_FMT(VIDEO,
"Asset '{}' error - failed to load the json file '{}', due to parse error: {}",
asset_id, PathToString(metadata->second), error);
return {};
}
if (!root.is<picojson::object>())
{
ERROR_LOG_FMT(
VIDEO,
"Asset '{}' error - failed to load the json file '{}', due to root not being an object!",
asset_id, PathToString(metadata->second));
return {};
}
const auto& root_obj = root.get<picojson::object>();
if (!TextureAndSamplerData::FromJson(asset_id, root_obj, data))
{
return {};
}
}
else
{
data->sampler = RenderState::GetLinearSamplerState();
data->type = AbstractTextureType::Texture_2D;
}
if (!LoadTextureDataFromFile(asset_id, texture_path->second, data->type, &data->texture_data))
return {};
if (!PurgeInvalidMipsFromTextureData(asset_id, &data->texture_data))
return {};
return LoadInfo{GetAssetSize(data->texture_data) + metadata_size};
}
void DirectFilesystemAssetLibrary::SetAssetIDMapData(const AssetID& asset_id,
VideoCommon::Assets::AssetMap asset_path_map)
{
@@ -20,11 +20,7 @@ namespace VideoCommon
class DirectFilesystemAssetLibrary final : public WatchableFilesystemAssetLibrary
{
public:
LoadInfo LoadTexture(const AssetID& asset_id, TextureAndSamplerData* data) override;
LoadInfo LoadTexture(const AssetID& asset_id, CustomTextureData* data) override;
LoadInfo LoadRasterSurfaceShader(const AssetID& asset_id, RasterSurfaceShaderData* data) override;
LoadInfo LoadMaterial(const AssetID& asset_id, MaterialData* data) override;
LoadInfo LoadMesh(const AssetID& asset_id, MeshData* data) override;
// Assigns the asset id to a map of files, how this map is read is dependent on the data
// For instance, a raw texture would expect the map to have a single entry and load that
@@ -1,482 +0,0 @@
// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "VideoCommon/Assets/MaterialAsset.h"
#include <algorithm>
#include <string_view>
#include <vector>
#include "Common/JsonUtil.h"
#include "Common/Logging/Log.h"
#include "Common/StringUtil.h"
#include "Common/VariantUtil.h"
#include "VideoCommon/Assets/CustomAssetLibrary.h"
namespace VideoCommon
{
namespace
{
// While not optimal, we pad our data to match std140 shader requirements
// this memory constant indicates the memory stride for a single uniform
// regardless of data type
constexpr std::size_t MemorySize = sizeof(float) * 4;
template <typename ElementType, std::size_t ElementCount>
bool ParseNumeric(const CustomAssetLibrary::AssetID& asset_id, const picojson::value& json_value,
MaterialProperty::Value* value)
{
static_assert(ElementCount <= 4, "Numeric data expected to be four elements or less");
if constexpr (ElementCount == 1)
{
if (!json_value.is<double>())
{
ERROR_LOG_FMT(VIDEO,
"Asset id '{}' material has attribute where "
"a double was expected but not provided.",
asset_id);
return false;
}
*value = static_cast<ElementType>(json_value.get<double>());
}
else
{
if (!json_value.is<picojson::array>())
{
ERROR_LOG_FMT(VIDEO,
"Asset id '{}' material has attribute where "
"an array was expected but not provided.",
asset_id);
return false;
}
const auto json_data = json_value.get<picojson::array>();
if (json_data.size() != ElementCount)
{
ERROR_LOG_FMT(VIDEO,
"Asset id '{}' material has attribute with incorrect number "
"of elements, expected {}",
asset_id, ElementCount);
return false;
}
if (!std::ranges::all_of(json_data, &picojson::value::is<double>))
{
ERROR_LOG_FMT(VIDEO,
"Asset id '{}' material has attribute where "
"all elements are not of type double.",
asset_id);
return false;
}
std::array<ElementType, ElementCount> data;
for (std::size_t i = 0; i < ElementCount; i++)
{
data[i] = static_cast<ElementType>(json_data[i].get<double>());
}
*value = std::move(data);
}
return true;
}
bool ParsePropertyValue(const CustomAssetLibrary::AssetID& asset_id,
const picojson::value& json_value, std::string_view type,
MaterialProperty::Value* value)
{
if (type == "int")
{
return ParseNumeric<s32, 1>(asset_id, json_value, value);
}
else if (type == "int2")
{
return ParseNumeric<s32, 2>(asset_id, json_value, value);
}
else if (type == "int3")
{
return ParseNumeric<s32, 3>(asset_id, json_value, value);
}
else if (type == "int4")
{
return ParseNumeric<s32, 4>(asset_id, json_value, value);
}
else if (type == "float")
{
return ParseNumeric<float, 1>(asset_id, json_value, value);
}
else if (type == "float2")
{
return ParseNumeric<float, 2>(asset_id, json_value, value);
}
else if (type == "float3")
{
return ParseNumeric<float, 3>(asset_id, json_value, value);
}
else if (type == "float4")
{
return ParseNumeric<float, 4>(asset_id, json_value, value);
}
else if (type == "bool")
{
if (json_value.is<bool>())
{
*value = json_value.get<bool>();
return true;
}
}
ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse the json, value is not valid for type '{}'",
asset_id, type);
return false;
}
bool ParseMaterialProperties(const CustomAssetLibrary::AssetID& asset_id,
const picojson::array& values_data,
std::vector<MaterialProperty>* material_property)
{
for (const auto& value_data : values_data)
{
VideoCommon::MaterialProperty property;
if (!value_data.is<picojson::object>())
{
ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse the json, value is not the right json type",
asset_id);
return false;
}
const auto& value_data_obj = value_data.get<picojson::object>();
const auto type_iter = value_data_obj.find("type");
if (type_iter == value_data_obj.end())
{
ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse the json, value entry 'type' not found",
asset_id);
return false;
}
if (!type_iter->second.is<std::string>())
{
ERROR_LOG_FMT(VIDEO,
"Asset '{}' failed to parse the json, value entry 'type' is not "
"the right json type",
asset_id);
return false;
}
std::string type = type_iter->second.to_str();
Common::ToLower(&type);
const auto value_iter = value_data_obj.find("value");
if (value_iter != value_data_obj.end())
{
if (!ParsePropertyValue(asset_id, value_iter->second, type, &property.m_value))
{
return false;
}
}
material_property->push_back(std::move(property));
}
return true;
}
} // namespace
void MaterialProperty::WriteToMemory(u8*& buffer, const MaterialProperty& property)
{
const auto write_memory = [&](const void* raw_value, std::size_t data_size) {
std::memcpy(buffer, raw_value, data_size);
std::memset(buffer + data_size, 0, MemorySize - data_size);
buffer += MemorySize;
};
std::visit(
overloaded{
[&](s32 value) { write_memory(&value, sizeof(s32)); },
[&](const std::array<s32, 2>& value) { write_memory(value.data(), sizeof(s32) * 2); },
[&](const std::array<s32, 3>& value) { write_memory(value.data(), sizeof(s32) * 3); },
[&](const std::array<s32, 4>& value) { write_memory(value.data(), sizeof(s32) * 4); },
[&](float value) { write_memory(&value, sizeof(float)); },
[&](const std::array<float, 2>& value) { write_memory(value.data(), sizeof(float) * 2); },
[&](const std::array<float, 3>& value) { write_memory(value.data(), sizeof(float) * 3); },
[&](const std::array<float, 4>& value) { write_memory(value.data(), sizeof(float) * 4); },
// Bool has the size of an int in the shader
[&](bool value) {
u32 val = static_cast<u32>(value);
write_memory(&val, sizeof(u32));
}},
property.m_value);
}
std::size_t MaterialProperty::GetMemorySize(const MaterialProperty& property)
{
std::size_t result = 0;
std::visit(overloaded{[&](s32 value) { result = MemorySize; },
[&](const std::array<s32, 2>&) { result = MemorySize; },
[&](const std::array<s32, 3>&) { result = MemorySize; },
[&](const std::array<s32, 4>&) { result = MemorySize; },
[&](float) { result = MemorySize; },
[&](const std::array<float, 2>&) { result = MemorySize; },
[&](const std::array<float, 3>&) { result = MemorySize; },
[&](const std::array<float, 4>&) { result = MemorySize; },
[&](bool) { result = MemorySize; }},
property.m_value);
return result;
}
bool MaterialData::FromJson(const CustomAssetLibrary::AssetID& asset_id,
const picojson::object& json, MaterialData* data)
{
const auto parse_properties = [&](const char* name,
std::vector<MaterialProperty>* material_properties) -> bool {
const auto properties_iter = json.find(name);
if (properties_iter == json.end())
{
ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, '{}' not found", asset_id, name);
return false;
}
if (!properties_iter->second.is<picojson::array>())
{
ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, '{}' is not the right json type",
asset_id, name);
return false;
}
const auto& properties_array = properties_iter->second.get<picojson::array>();
if (!ParseMaterialProperties(asset_id, properties_array, material_properties))
return false;
return true;
};
if (!parse_properties("properties", &data->properties))
return false;
if (const auto shader_asset = ReadStringFromJson(json, "shader_asset"))
{
data->shader_asset = *shader_asset;
}
else
{
ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, 'shader_asset' not found or wrong type",
asset_id);
return false;
}
if (const auto next_material_asset = ReadStringFromJson(json, "next_material_asset"))
{
data->next_material_asset = *next_material_asset;
}
else
{
ERROR_LOG_FMT(VIDEO,
"Asset '{}' failed to parse json, 'next_material_asset' not found or wrong type",
asset_id);
return false;
}
if (const auto cull_mode = ReadNumericFromJson<CullMode>(json, "cull_mode"))
{
data->cull_mode = *cull_mode;
}
if (const auto depth_state = json.find("depth_state");
depth_state != json.end() && depth_state->second.is<picojson::object>())
{
auto& json_obj = depth_state->second.get<picojson::object>();
DepthState state;
state.test_enable = ReadNumericFromJson<u32>(json_obj, "test_enable").value_or(0);
state.update_enable = ReadNumericFromJson<u32>(json_obj, "update_enable").value_or(0);
state.func = ReadNumericFromJson<CompareMode>(json_obj, "func").value_or(CompareMode::Never);
data->depth_state = state;
}
if (const auto blending_state = json.find("blending_state");
blending_state != json.end() && blending_state->second.is<picojson::object>())
{
auto& json_obj = blending_state->second.get<picojson::object>();
BlendingState state;
state.blend_enable = ReadNumericFromJson<u32>(json_obj, "blend_enable").value_or(0);
state.logic_op_enable = ReadNumericFromJson<u32>(json_obj, "logic_op_enable").value_or(0);
state.color_update = ReadNumericFromJson<u32>(json_obj, "color_update").value_or(0);
state.alpha_update = ReadNumericFromJson<u32>(json_obj, "alpha_update").value_or(0);
state.subtract = ReadNumericFromJson<u32>(json_obj, "subtract").value_or(0);
state.subtract_alpha = ReadNumericFromJson<u32>(json_obj, "subtract_alpha").value_or(0);
state.use_dual_src = ReadNumericFromJson<u32>(json_obj, "use_dual_src").value_or(0);
state.dst_factor =
ReadNumericFromJson<DstBlendFactor>(json_obj, "dst_factor").value_or(DstBlendFactor::Zero);
state.src_factor =
ReadNumericFromJson<SrcBlendFactor>(json_obj, "src_factor").value_or(SrcBlendFactor::Zero);
state.dst_factor_alpha = ReadNumericFromJson<DstBlendFactor>(json_obj, "dst_factor_alpha")
.value_or(DstBlendFactor::Zero);
state.src_factor_alpha = ReadNumericFromJson<SrcBlendFactor>(json_obj, "src_factor_alpha")
.value_or(SrcBlendFactor::Zero);
state.logic_mode =
ReadNumericFromJson<LogicOp>(json_obj, "logic_mode").value_or(LogicOp::Clear);
data->blending_state = state;
}
const auto textures_iter = json.find("textures");
if (textures_iter == json.end())
{
ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, 'textures' not found", asset_id);
return false;
}
if (!textures_iter->second.is<picojson::array>())
{
ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, 'textures' is not the right json type",
asset_id);
return false;
}
const auto& textures_array = textures_iter->second.get<picojson::array>();
if (!std::ranges::all_of(textures_array, [](const picojson::value& json_data) {
return json_data.is<picojson::object>();
}))
{
ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, 'textures' must contain objects",
asset_id);
return false;
}
for (const auto& texture_json : textures_array)
{
auto& texture_json_obj = texture_json.get<picojson::object>();
TextureSamplerValue sampler_value;
if (!TextureSamplerValue::FromJson(texture_json_obj, &sampler_value))
return false;
data->textures.push_back(std::move(sampler_value));
}
return true;
}
void MaterialData::ToJson(picojson::object* obj, const MaterialData& data)
{
if (!obj) [[unlikely]]
return;
auto& json_obj = *obj;
const auto add_property = [](picojson::array* json_properties, const MaterialProperty& property) {
picojson::object json_property;
std::visit(overloaded{[&](s32 value) {
json_property["type"] = picojson::value{"int"};
json_property["value"] = picojson::value{static_cast<double>(value)};
},
[&](const std::array<s32, 2>& value) {
json_property["type"] = picojson::value{"int2"};
json_property["value"] = picojson::value{ToJsonArray(value)};
},
[&](const std::array<s32, 3>& value) {
json_property["type"] = picojson::value{"int3"};
json_property["value"] = picojson::value{ToJsonArray(value)};
},
[&](const std::array<s32, 4>& value) {
json_property["type"] = picojson::value{"int4"};
json_property["value"] = picojson::value{ToJsonArray(value)};
},
[&](float value) {
json_property["type"] = picojson::value{"float"};
json_property["value"] = picojson::value{static_cast<double>(value)};
},
[&](const std::array<float, 2>& value) {
json_property["type"] = picojson::value{"float2"};
json_property["value"] = picojson::value{ToJsonArray(value)};
},
[&](const std::array<float, 3>& value) {
json_property["type"] = picojson::value{"float3"};
json_property["value"] = picojson::value{ToJsonArray(value)};
},
[&](const std::array<float, 4>& value) {
json_property["type"] = picojson::value{"float4"};
json_property["value"] = picojson::value{ToJsonArray(value)};
},
[&](bool value) {
json_property["type"] = picojson::value{"bool"};
json_property["value"] = picojson::value{value};
}},
property.m_value);
json_properties->emplace_back(std::move(json_property));
};
picojson::array json_properties;
for (const auto& property : data.properties)
{
add_property(&json_properties, property);
}
json_obj.emplace("properties", std::move(json_properties));
json_obj.emplace("shader_asset", data.shader_asset);
json_obj.emplace("next_material_asset", data.next_material_asset);
if (data.cull_mode)
{
json_obj.emplace("cull_mode", static_cast<double>(*data.cull_mode));
}
if (data.depth_state)
{
picojson::object depth_state_json;
depth_state_json.emplace("test_enable",
static_cast<double>(data.depth_state->test_enable.Value()));
depth_state_json.emplace("update_enable",
static_cast<double>(data.depth_state->update_enable.Value()));
depth_state_json.emplace("func", static_cast<double>(data.depth_state->func.Value()));
json_obj.emplace("depth_state", depth_state_json);
}
if (data.blending_state)
{
picojson::object blending_state_json;
blending_state_json.emplace("blend_enable",
static_cast<double>(data.blending_state->blend_enable.Value()));
blending_state_json.emplace("logic_op_enable",
static_cast<double>(data.blending_state->logic_op_enable.Value()));
blending_state_json.emplace("color_update",
static_cast<double>(data.blending_state->color_update.Value()));
blending_state_json.emplace("alpha_update",
static_cast<double>(data.blending_state->alpha_update.Value()));
blending_state_json.emplace("subtract",
static_cast<double>(data.blending_state->subtract.Value()));
blending_state_json.emplace("subtract_alpha",
static_cast<double>(data.blending_state->subtract_alpha.Value()));
blending_state_json.emplace("use_dual_src",
static_cast<double>(data.blending_state->use_dual_src.Value()));
blending_state_json.emplace("dst_factor",
static_cast<double>(data.blending_state->dst_factor.Value()));
blending_state_json.emplace("src_factor",
static_cast<double>(data.blending_state->src_factor.Value()));
blending_state_json.emplace("dst_factor_alpha",
static_cast<double>(data.blending_state->dst_factor_alpha.Value()));
blending_state_json.emplace("src_factor_alpha",
static_cast<double>(data.blending_state->src_factor_alpha.Value()));
blending_state_json.emplace("logic_mode",
static_cast<double>(data.blending_state->logic_mode.Value()));
json_obj.emplace("blending_state", blending_state_json);
}
picojson::array json_textures;
for (const auto& texture : data.textures)
{
picojson::object json_texture;
TextureSamplerValue::ToJson(&json_texture, texture);
json_textures.emplace_back(std::move(json_texture));
}
json_obj.emplace("textures", json_textures);
}
CustomAssetLibrary::LoadInfo MaterialAsset::LoadImpl(const CustomAssetLibrary::AssetID& asset_id)
{
auto potential_data = std::make_shared<MaterialData>();
const auto loaded_info = m_owning_library->LoadMaterial(asset_id, potential_data.get());
if (loaded_info.bytes_loaded == 0)
return {};
{
std::lock_guard lk(m_data_lock);
m_loaded = true;
m_data = std::move(potential_data);
}
return loaded_info;
}
} // namespace VideoCommon
@@ -1,62 +0,0 @@
// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <optional>
#include <variant>
#include <vector>
#include <picojson.h>
#include "Common/CommonTypes.h"
#include "VideoCommon/Assets/CustomAsset.h"
#include "VideoCommon/Assets/TextureSamplerValue.h"
#include "VideoCommon/BPMemory.h"
#include "VideoCommon/RenderState.h"
class ShaderCode;
namespace VideoCommon
{
struct MaterialProperty
{
static void WriteToMemory(u8*& buffer, const MaterialProperty& property);
static std::size_t GetMemorySize(const MaterialProperty& property);
using Value =
std::variant<s32, std::array<s32, 2>, std::array<s32, 3>, std::array<s32, 4>, float,
std::array<float, 2>, std::array<float, 3>, std::array<float, 4>, bool>;
Value m_value;
};
struct MaterialData
{
static bool FromJson(const CustomAssetLibrary::AssetID& asset_id, const picojson::object& json,
MaterialData* data);
static void ToJson(picojson::object* obj, const MaterialData& data);
CustomAssetLibrary::AssetID shader_asset;
CustomAssetLibrary::AssetID next_material_asset;
std::vector<MaterialProperty> properties;
std::optional<CullMode> cull_mode;
std::optional<DepthState> depth_state;
std::optional<BlendingState> blending_state;
std::vector<TextureSamplerValue> textures;
};
// Much like Unity and Unreal materials, a Dolphin material does very little on its own
// Its sole purpose is to provide data (through properties) that are used in conjunction
// with a shader asset that is provided by name. It is up to user of this asset to
// use the two together to create the relevant runtime data
class MaterialAsset final : public CustomLoadableAsset<MaterialData>
{
public:
using CustomLoadableAsset::CustomLoadableAsset;
private:
CustomAssetLibrary::LoadInfo LoadImpl(const CustomAssetLibrary::AssetID& asset_id) override;
};
} // namespace VideoCommon
@@ -1,661 +0,0 @@
// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "VideoCommon/Assets/MeshAsset.h"
#include <array>
#include <utility>
#include <tinygltf/tiny_gltf.h>
#include "Common/IOFile.h"
#include "Common/Logging/Log.h"
#include "VideoCommon/Assets/CustomAssetLibrary.h"
namespace VideoCommon
{
namespace
{
Common::Matrix44 BuildMatrixFromNode(const tinygltf::Node& node)
{
if (!node.matrix.empty())
{
Common::Matrix44 matrix;
for (std::size_t i = 0; i < node.matrix.size(); i++)
{
matrix.data[i] = static_cast<float>(node.matrix[i]);
}
return matrix;
}
Common::Matrix44 matrix = Common::Matrix44::Identity();
// Check individual components
if (!node.scale.empty())
{
matrix *= Common::Matrix44::FromMatrix33(Common::Matrix33::Scale(
Common::Vec3{static_cast<float>(node.scale[0]), static_cast<float>(node.scale[1]),
static_cast<float>(node.scale[2])}));
}
if (!node.rotation.empty())
{
matrix *= Common::Matrix44::FromQuaternion(Common::Quaternion(
static_cast<float>(node.rotation[3]), static_cast<float>(node.rotation[0]),
static_cast<float>(node.rotation[1]), static_cast<float>(node.rotation[2])));
}
if (!node.translation.empty())
{
matrix *= Common::Matrix44::Translate(Common::Vec3{static_cast<float>(node.translation[0]),
static_cast<float>(node.translation[1]),
static_cast<float>(node.translation[2])});
}
return matrix;
}
bool GLTFComponentTypeToAttributeFormat(int component_type, AttributeFormat* format)
{
switch (component_type)
{
case TINYGLTF_COMPONENT_TYPE_BYTE:
{
format->type = ComponentFormat::Byte;
format->integer = false;
}
break;
case TINYGLTF_COMPONENT_TYPE_DOUBLE:
{
return false;
}
break;
case TINYGLTF_COMPONENT_TYPE_FLOAT:
{
format->type = ComponentFormat::Float;
format->integer = false;
}
break;
case TINYGLTF_COMPONENT_TYPE_INT:
{
format->type = ComponentFormat::Float;
format->integer = true;
}
break;
case TINYGLTF_COMPONENT_TYPE_SHORT:
{
format->type = ComponentFormat::Short;
format->integer = false;
}
break;
case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE:
{
format->type = ComponentFormat::UByte;
format->integer = false;
}
break;
case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT:
{
return false;
}
break;
case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT:
{
format->type = ComponentFormat::UShort;
format->integer = false;
}
break;
};
return true;
}
bool UpdateVertexStrideFromPrimitive(const tinygltf::Model& model, u32 accessor_index,
MeshDataChunk* chunk)
{
const tinygltf::Accessor& accessor = model.accessors[accessor_index];
const int component_count = tinygltf::GetNumComponentsInType(accessor.type);
if (component_count == -1)
{
ERROR_LOG_FMT(VIDEO, "Failed to update vertex stride, component count was invalid");
return false;
}
const int component_size =
tinygltf::GetComponentSizeInBytes(static_cast<uint32_t>(accessor.componentType));
if (component_size == -1)
{
ERROR_LOG_FMT(VIDEO, "Failed to update vertex stride, component size was invalid");
return false;
}
chunk->vertex_stride += component_size * component_count;
return true;
}
bool CopyBufferDataFromPrimitive(const tinygltf::Model& model, u32 accessor_index,
std::size_t* outbound_offset, MeshDataChunk* chunk)
{
const tinygltf::Accessor& accessor = model.accessors[accessor_index];
const int component_count = tinygltf::GetNumComponentsInType(accessor.type);
if (component_count == -1)
{
ERROR_LOG_FMT(VIDEO, "Failed to copy buffer data from primitive, component count was invalid");
return false;
}
const int component_size =
tinygltf::GetComponentSizeInBytes(static_cast<uint32_t>(accessor.componentType));
if (component_size == -1)
{
ERROR_LOG_FMT(VIDEO, "Failed to copy buffer data from primitive, component size was invalid");
return false;
}
const tinygltf::BufferView& buffer_view = model.bufferViews[accessor.bufferView];
const tinygltf::Buffer& buffer = model.buffers[buffer_view.buffer];
if (buffer_view.byteStride == 0)
{
// Data is tightly packed
const auto data = &buffer.data[accessor.byteOffset + buffer_view.byteOffset];
for (std::size_t i = 0; i < accessor.count; i++)
{
const std::size_t vertex_data_offset = i * chunk->vertex_stride + *outbound_offset;
memcpy(&chunk->vertex_data[vertex_data_offset], &data[i * component_size * component_count],
component_size * component_count);
}
}
else
{
// Data is interleaved
const auto data = &buffer.data[accessor.byteOffset + buffer_view.byteOffset];
for (std::size_t i = 0; i < accessor.count; i++)
{
const std::size_t vertex_data_offset = i * chunk->vertex_stride + *outbound_offset;
const std::size_t gltf_data_offset = i * buffer_view.byteStride;
memcpy(&chunk->vertex_data[vertex_data_offset], &data[gltf_data_offset],
component_size * component_count);
}
}
*outbound_offset += component_size * component_count;
return true;
}
bool ReadGLTFMesh(std::string_view mesh_file, const tinygltf::Model& model,
const tinygltf::Mesh& mesh, const Common::Matrix44& mat, MeshData* data)
{
for (std::size_t primitive_index = 0; primitive_index < mesh.primitives.size(); ++primitive_index)
{
MeshDataChunk chunk;
chunk.transform = mat;
const tinygltf::Primitive& primitive = mesh.primitives[primitive_index];
if (primitive.indices == -1)
{
ERROR_LOG_FMT(VIDEO, "Mesh '{}' is expected to have indices but doesn't have any", mesh_file);
return false;
}
chunk.material_name = model.materials[primitive.material].name;
const tinygltf::Accessor& index_accessor = model.accessors[primitive.indices];
const tinygltf::BufferView& index_buffer_view = model.bufferViews[index_accessor.bufferView];
const tinygltf::Buffer& index_buffer = model.buffers[index_buffer_view.buffer];
const int index_stride = index_accessor.ByteStride(index_buffer_view);
if (index_stride == -1)
{
ERROR_LOG_FMT(VIDEO, "Mesh '{}' has invalid stride", mesh_file);
return false;
}
// TODO C++23: use make_unique_overwrite
chunk.indices = std::unique_ptr<u16[]>(new u16[index_accessor.count]);
auto index_src = &index_buffer.data[index_accessor.byteOffset + index_buffer_view.byteOffset];
for (std::size_t i = 0; i < index_accessor.count; i++)
{
if (index_accessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT)
{
std::memcpy(&chunk.indices[i], &index_src[i * index_stride], sizeof(u16));
}
else if (index_accessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE)
{
u8 unsigned_byte;
std::memcpy(&unsigned_byte, &index_src[i * index_stride], sizeof(u8));
chunk.indices[i] = unsigned_byte;
}
else if (index_accessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT)
{
// TODO: update Dolphin to support u32 indices
ERROR_LOG_FMT(
VIDEO,
"Mesh '{}' uses an indice format of unsigned int which is not currently supported",
mesh_file);
return false;
}
}
chunk.num_indices = static_cast<u32>(index_accessor.count);
if (primitive.mode == TINYGLTF_MODE_TRIANGLES)
{
chunk.primitive_type = PrimitiveType::Triangles;
}
else if (primitive.mode == TINYGLTF_MODE_TRIANGLE_STRIP)
{
chunk.primitive_type = PrimitiveType::TriangleStrip;
}
else if (primitive.mode == TINYGLTF_MODE_TRIANGLE_FAN)
{
ERROR_LOG_FMT(VIDEO, "Mesh '{}' requires triangle fan but that is not supported", mesh_file);
return false;
}
else if (primitive.mode == TINYGLTF_MODE_LINE)
{
chunk.primitive_type = PrimitiveType::Lines;
}
else if (primitive.mode == TINYGLTF_MODE_POINTS)
{
chunk.primitive_type = PrimitiveType::Points;
}
chunk.vertex_stride = 0;
static constexpr std::array<std::string_view, 12> all_names = {
"POSITION", "NORMAL", "COLOR_0", "COLOR_1", "TEXCOORD_0", "TEXCOORD_1",
"TEXCOORD_2", "TEXCOORD_3", "TEXCOORD_4", "TEXCOORD_5", "TEXCOORD_6", "TEXCOORD_7",
};
for (std::size_t i = 0; i < all_names.size(); i++)
{
const auto it = primitive.attributes.find(std::string{all_names[i]});
if (it != primitive.attributes.end())
{
if (!UpdateVertexStrideFromPrimitive(model, it->second, &chunk))
return false;
}
}
chunk.vertex_declaration.stride = chunk.vertex_stride;
const auto position_it = primitive.attributes.find("POSITION");
if (position_it == primitive.attributes.end())
{
ERROR_LOG_FMT(VIDEO, "Mesh '{}' does not provide a POSITION attribute, that is required",
mesh_file);
return false;
}
std::size_t outbound_offset = 0;
const tinygltf::Accessor& pos_accessor = model.accessors[position_it->second];
chunk.num_vertices = static_cast<u32>(pos_accessor.count);
// TODO C++23: use make_unique_overwrite
chunk.vertex_data = std::unique_ptr<u8[]>(new u8[chunk.num_vertices * chunk.vertex_stride]);
if (!CopyBufferDataFromPrimitive(model, position_it->second, &outbound_offset, &chunk))
return false;
chunk.components_available = 0;
chunk.vertex_declaration.position.enable = true;
chunk.vertex_declaration.position.components = 3;
chunk.vertex_declaration.position.offset = 0;
if (!GLTFComponentTypeToAttributeFormat(pos_accessor.componentType,
&chunk.vertex_declaration.position))
{
ERROR_LOG_FMT(VIDEO, "Mesh '{}' has invalid attribute format for position", mesh_file);
return false;
}
// Save off min and max position in case we want to compute bounds
// GLTF spec expects these values to exist but error if they don't
if (pos_accessor.minValues.size() != 3)
{
ERROR_LOG_FMT(VIDEO, "Mesh '{}' is expected to have a minimum value but it is missing",
mesh_file);
return false;
}
chunk.minimum_position.x = static_cast<float>(pos_accessor.minValues[0]);
chunk.minimum_position.y = static_cast<float>(pos_accessor.minValues[1]);
chunk.minimum_position.z = static_cast<float>(pos_accessor.minValues[2]);
if (pos_accessor.maxValues.size() != 3)
{
ERROR_LOG_FMT(VIDEO, "Mesh '{}' is expected to have a maximum value but it is missing",
mesh_file);
return false;
}
chunk.maximum_position.x = static_cast<float>(pos_accessor.maxValues[0]);
chunk.maximum_position.y = static_cast<float>(pos_accessor.maxValues[1]);
chunk.maximum_position.z = static_cast<float>(pos_accessor.maxValues[2]);
static constexpr std::array<std::string_view, 2> color_names = {
"COLOR_0",
"COLOR_1",
};
for (std::size_t i = 0; i < color_names.size(); i++)
{
const auto color_it = primitive.attributes.find(std::string{color_names[i]});
if (color_it != primitive.attributes.end())
{
chunk.vertex_declaration.colors[i].offset = static_cast<int>(outbound_offset);
if (!CopyBufferDataFromPrimitive(model, color_it->second, &outbound_offset, &chunk))
return false;
chunk.components_available |= VB_HAS_COL0 << i;
chunk.vertex_declaration.colors[i].enable = true;
chunk.vertex_declaration.colors[i].components = 3;
const tinygltf::Accessor& accessor = model.accessors[color_it->second];
if (!GLTFComponentTypeToAttributeFormat(accessor.componentType,
&chunk.vertex_declaration.colors[i]))
{
ERROR_LOG_FMT(VIDEO, "Mesh '{}' has invalid attribute format for {}", mesh_file,
color_names[i]);
return false;
}
}
else
{
chunk.vertex_declaration.colors[i].enable = false;
}
}
const auto normal_it = primitive.attributes.find("NORMAL");
if (normal_it != primitive.attributes.end())
{
chunk.vertex_declaration.normals[0].offset = static_cast<int>(outbound_offset);
if (!CopyBufferDataFromPrimitive(model, normal_it->second, &outbound_offset, &chunk))
return false;
chunk.components_available |= VB_HAS_NORMAL;
chunk.vertex_declaration.normals[0].enable = true;
chunk.vertex_declaration.normals[0].components = 3;
const tinygltf::Accessor& accessor = model.accessors[normal_it->second];
if (!GLTFComponentTypeToAttributeFormat(accessor.componentType,
&chunk.vertex_declaration.normals[0]))
{
ERROR_LOG_FMT(VIDEO, "Mesh '{}' has invalid attribute format for NORMAL", mesh_file);
return false;
}
}
else
{
chunk.vertex_declaration.normals[0].enable = false;
}
static constexpr std::array<std::string_view, 8> texcoord_names = {
"TEXCOORD_0", "TEXCOORD_1", "TEXCOORD_2", "TEXCOORD_3",
"TEXCOORD_4", "TEXCOORD_5", "TEXCOORD_6", "TEXCOORD_7",
};
for (std::size_t i = 0; i < texcoord_names.size(); i++)
{
const auto texture_it = primitive.attributes.find(std::string{texcoord_names[i]});
if (texture_it != primitive.attributes.end())
{
chunk.vertex_declaration.texcoords[i].offset = static_cast<int>(outbound_offset);
if (!CopyBufferDataFromPrimitive(model, texture_it->second, &outbound_offset, &chunk))
return false;
chunk.components_available |= VB_HAS_UV0 << i;
chunk.vertex_declaration.texcoords[i].enable = true;
chunk.vertex_declaration.texcoords[i].components = 2;
const tinygltf::Accessor& accessor = model.accessors[texture_it->second];
if (!GLTFComponentTypeToAttributeFormat(accessor.componentType,
&chunk.vertex_declaration.texcoords[i]))
{
ERROR_LOG_FMT(VIDEO, "Mesh '{}' has invalid attribute format for {}", mesh_file,
texcoord_names[i]);
return false;
}
}
else
{
chunk.vertex_declaration.texcoords[i].enable = false;
}
}
// Position matrix can be enabled if the draw that is using
// this mesh needs it
chunk.vertex_declaration.posmtx.enable = false;
data->m_mesh_chunks.push_back(std::move(chunk));
}
return true;
}
bool ReadGLTFNodes(std::string_view mesh_file, const tinygltf::Model& model,
const tinygltf::Node& node, const Common::Matrix44& mat, MeshData* data)
{
if (node.mesh != -1)
{
if (!ReadGLTFMesh(mesh_file, model, model.meshes[node.mesh], mat, data))
return false;
}
for (std::size_t i = 0; i < node.children.size(); i++)
{
const tinygltf::Node& child = model.nodes[node.children[i]];
const auto child_mat = mat * BuildMatrixFromNode(child);
if (!ReadGLTFNodes(mesh_file, model, child, child_mat, data))
return false;
}
return true;
}
bool ReadGLTFMaterials(std::string_view mesh_file, const tinygltf::Model& model, MeshData* data)
{
for (std::size_t i = 0; i < model.materials.size(); i++)
{
const tinygltf::Material& material = model.materials[i];
// TODO: support converting material data into Dolphin material assets
data->m_mesh_material_to_material_asset_id.insert_or_assign(material.name, "");
}
return true;
}
bool ReadGLTF(std::string_view mesh_file, const tinygltf::Model& model, MeshData* data)
{
int scene_index = model.defaultScene;
if (scene_index == -1)
scene_index = 0;
const auto& scene = model.scenes[scene_index];
const auto scene_node_indices = scene.nodes;
for (std::size_t i = 0; i < scene_node_indices.size(); i++)
{
const tinygltf::Node& node = model.nodes[scene_node_indices[i]];
const auto mat = BuildMatrixFromNode(node);
if (!ReadGLTFNodes(mesh_file, model, node, mat, data))
return false;
}
return ReadGLTFMaterials(mesh_file, model, data);
}
} // namespace
bool MeshData::FromJson(const VideoCommon::CustomAssetLibrary::AssetID& asset_id,
const picojson::object& json, MeshData* data)
{
if (const auto iter = json.find("material_mapping"); iter != json.end())
{
if (!iter->second.is<picojson::object>())
{
ERROR_LOG_FMT(
VIDEO,
"Asset '{}' failed to parse json, expected 'material_mapping' to be of type object",
asset_id);
return false;
}
for (const auto& [material_name, asset_id_json] : iter->second.get<picojson::object>())
{
if (!asset_id_json.is<std::string>())
{
ERROR_LOG_FMT(
VIDEO,
"Asset '{}' failed to parse json, material name '{}' linked to a non-string value",
asset_id, material_name);
return false;
}
data->m_mesh_material_to_material_asset_id[material_name] = asset_id_json.to_str();
}
}
return true;
}
void MeshData::ToJson(picojson::object& obj, const MeshData& data)
{
picojson::object material_mapping;
for (const auto& [material_name, asset_id] : data.m_mesh_material_to_material_asset_id)
{
material_mapping.emplace(material_name, asset_id);
}
obj.emplace("material_mapping", std::move(material_mapping));
}
bool MeshData::FromDolphinMesh(std::span<const u8> raw_data, MeshData* data)
{
std::size_t offset = 0;
std::size_t chunk_size = 0;
std::memcpy(&chunk_size, raw_data.data(), sizeof(std::size_t));
offset += sizeof(std::size_t);
data->m_mesh_chunks.reserve(chunk_size);
for (std::size_t i = 0; i < chunk_size; i++)
{
MeshDataChunk chunk;
std::memcpy(&chunk.num_vertices, raw_data.data() + offset, sizeof(u32));
offset += sizeof(u32);
std::memcpy(&chunk.vertex_stride, raw_data.data() + offset, sizeof(u32));
offset += sizeof(u32);
// TODO C++23: use make_unique_overwrite
chunk.vertex_data = std::unique_ptr<u8[]>(new u8[chunk.num_vertices * chunk.vertex_stride]);
std::memcpy(chunk.vertex_data.get(), raw_data.data() + offset,
chunk.num_vertices * chunk.vertex_stride);
offset += chunk.num_vertices * chunk.vertex_stride;
std::memcpy(&chunk.num_indices, raw_data.data() + offset, sizeof(u32));
offset += sizeof(u32);
// TODO C++23: use make_unique_overwrite
chunk.indices = std::unique_ptr<u16[]>(new u16[chunk.num_indices]);
std::memcpy(chunk.indices.get(), raw_data.data() + offset, chunk.num_indices * sizeof(u16));
offset += chunk.num_indices * sizeof(u16);
std::memcpy(&chunk.vertex_declaration, raw_data.data() + offset,
sizeof(PortableVertexDeclaration));
offset += sizeof(PortableVertexDeclaration);
std::memcpy(&chunk.primitive_type, raw_data.data() + offset, sizeof(PrimitiveType));
offset += sizeof(PrimitiveType);
std::memcpy(&chunk.components_available, raw_data.data() + offset, sizeof(u32));
offset += sizeof(u32);
std::memcpy(&chunk.minimum_position, raw_data.data() + offset, sizeof(Common::Vec3));
offset += sizeof(Common::Vec3);
std::memcpy(&chunk.maximum_position, raw_data.data() + offset, sizeof(Common::Vec3));
offset += sizeof(Common::Vec3);
std::memcpy(&chunk.transform.data[0], raw_data.data() + offset,
chunk.transform.data.size() * sizeof(float));
offset += chunk.transform.data.size() * sizeof(float);
std::size_t material_name_size = 0;
std::memcpy(&material_name_size, raw_data.data() + offset, sizeof(std::size_t));
offset += sizeof(std::size_t);
chunk.material_name.assign(raw_data.data() + offset,
raw_data.data() + offset + material_name_size);
offset += material_name_size * sizeof(char);
data->m_mesh_chunks.push_back(std::move(chunk));
}
return true;
}
bool MeshData::ToDolphinMesh(File::IOFile* file_data, const MeshData& data)
{
const std::size_t chunk_size = data.m_mesh_chunks.size();
file_data->WriteBytes(&chunk_size, sizeof(std::size_t));
for (const auto& chunk : data.m_mesh_chunks)
{
if (!file_data->WriteBytes(&chunk.num_vertices, sizeof(u32)))
return false;
if (!file_data->WriteBytes(&chunk.vertex_stride, sizeof(u32)))
return false;
if (!file_data->WriteBytes(chunk.vertex_data.get(), chunk.num_vertices * chunk.vertex_stride))
return false;
if (!file_data->WriteBytes(&chunk.num_indices, sizeof(u32)))
return false;
if (!file_data->WriteBytes(chunk.indices.get(), chunk.num_indices * sizeof(u16)))
return false;
if (!file_data->WriteBytes(&chunk.vertex_declaration, sizeof(PortableVertexDeclaration)))
return false;
if (!file_data->WriteBytes(&chunk.primitive_type, sizeof(PrimitiveType)))
return false;
if (!file_data->WriteBytes(&chunk.components_available, sizeof(u32)))
return false;
if (!file_data->WriteBytes(&chunk.minimum_position, sizeof(Common::Vec3)))
return false;
if (!file_data->WriteBytes(&chunk.maximum_position, sizeof(Common::Vec3)))
return false;
if (!file_data->WriteBytes(&chunk.transform.data[0],
chunk.transform.data.size() * sizeof(float)))
{
return false;
}
const std::size_t material_name_size = chunk.material_name.size();
if (!file_data->WriteBytes(&material_name_size, sizeof(std::size_t)))
return false;
if (!file_data->WriteBytes(&chunk.material_name[0], chunk.material_name.size() * sizeof(char)))
return false;
}
return true;
}
bool MeshData::FromGLTF(std::string_view gltf_file, MeshData* data)
{
if (gltf_file.ends_with(".glb"))
{
ERROR_LOG_FMT(VIDEO, "File '{}' with glb extension is not supported at this time", gltf_file);
return false;
}
if (gltf_file.ends_with(".gltf"))
{
tinygltf::Model model;
tinygltf::TinyGLTF loader;
std::string model_errors;
std::string model_warnings;
if (!loader.LoadASCIIFromFile(&model, &model_errors, &model_warnings, std::string{gltf_file}))
{
ERROR_LOG_FMT(VIDEO, "File '{}' was invalid GLTF, error: {}, warning: {}", gltf_file,
model_errors, model_warnings);
return false;
}
return ReadGLTF(gltf_file, model, data);
}
ERROR_LOG_FMT(VIDEO, "GLTF '{}' has invalid extension", gltf_file);
return false;
}
CustomAssetLibrary::LoadInfo MeshAsset::LoadImpl(const CustomAssetLibrary::AssetID& asset_id)
{
auto potential_data = std::make_shared<MeshData>();
const auto loaded_info = m_owning_library->LoadMesh(asset_id, potential_data.get());
if (loaded_info.bytes_loaded == 0)
return {};
{
std::lock_guard lk(m_data_lock);
m_loaded = true;
m_data = std::move(potential_data);
}
return loaded_info;
}
} // namespace VideoCommon
@@ -1,69 +0,0 @@
// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <map>
#include <memory>
#include <span>
#include <string>
#include <string_view>
#include <picojson.h>
#include "Common/CommonTypes.h"
#include "Common/Matrix.h"
#include "VideoCommon/Assets/CustomAsset.h"
#include "VideoCommon/NativeVertexFormat.h"
#include "VideoCommon/RenderState.h"
namespace File
{
class IOFile;
}
namespace VideoCommon
{
struct MeshDataChunk
{
std::unique_ptr<u8[]> vertex_data;
u32 vertex_stride;
u32 num_vertices;
std::unique_ptr<u16[]> indices;
u32 num_indices;
PortableVertexDeclaration vertex_declaration;
PrimitiveType primitive_type;
u32 components_available;
Common::Vec3 minimum_position;
Common::Vec3 maximum_position;
Common::Matrix44 transform;
std::string material_name;
};
struct MeshData
{
static bool FromJson(const CustomAssetLibrary::AssetID& asset_id, const picojson::object& json,
MeshData* data);
static void ToJson(picojson::object& obj, const MeshData& data);
static bool FromDolphinMesh(std::span<const u8> raw_data, MeshData* data);
static bool ToDolphinMesh(File::IOFile* file_data, const MeshData& data);
static bool FromGLTF(std::string_view gltf_file, MeshData* data);
std::vector<MeshDataChunk> m_mesh_chunks;
std::map<std::string, CustomAssetLibrary::AssetID, std::less<>>
m_mesh_material_to_material_asset_id;
};
class MeshAsset final : public CustomLoadableAsset<MeshData>
{
public:
using CustomLoadableAsset::CustomLoadableAsset;
private:
CustomAssetLibrary::LoadInfo LoadImpl(const CustomAssetLibrary::AssetID& asset_id) override;
};
} // namespace VideoCommon
@@ -1,509 +0,0 @@
// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "VideoCommon/Assets/ShaderAsset.h"
#include <algorithm>
#include <utility>
#include "Common/JsonUtil.h"
#include "Common/Logging/Log.h"
#include "Common/StringUtil.h"
#include "Common/VariantUtil.h"
#include "VideoCommon/Assets/CustomAssetLibrary.h"
#include "VideoCommon/ShaderGenCommon.h"
namespace VideoCommon
{
template <typename ElementType, std::size_t ElementCount, typename PropertyType>
bool ParseNumeric(const CustomAssetLibrary::AssetID& asset_id, const picojson::value& json_value,
std::string_view code_name, PropertyType* value)
{
static_assert(ElementCount <= 4, "Numeric data expected to be four elements or less");
if constexpr (ElementCount == 1)
{
if (!json_value.is<double>())
{
ERROR_LOG_FMT(VIDEO,
"Asset id '{}' shader has attribute '{}' where "
"a double was expected but not provided.",
asset_id, code_name);
return false;
}
*value = static_cast<ElementType>(json_value.get<double>());
}
else
{
if (!json_value.is<picojson::array>())
{
ERROR_LOG_FMT(VIDEO,
"Asset id '{}' shader has attribute '{}' where "
"an array was expected but not provided.",
asset_id, code_name);
return false;
}
const auto json_data = json_value.get<picojson::array>();
if (json_data.size() != ElementCount)
{
ERROR_LOG_FMT(VIDEO,
"Asset id '{}' shader has attribute '{}' with incorrect number "
"of elements, expected {}",
asset_id, code_name, ElementCount);
return false;
}
if (!std::ranges::all_of(json_data, &picojson::value::is<double>))
{
ERROR_LOG_FMT(VIDEO,
"Asset id '{}' shader has attribute '{}' where "
"all elements are not of type double.",
asset_id, code_name);
return false;
}
std::array<ElementType, ElementCount> data;
for (std::size_t i = 0; i < ElementCount; i++)
{
data[i] = static_cast<ElementType>(json_data[i].get<double>());
}
*value = std::move(data);
}
return true;
}
static bool ParseShaderValue(const CustomAssetLibrary::AssetID& asset_id,
const picojson::value& json_value, std::string_view code_name,
std::string_view type, ShaderProperty::Value* value)
{
if (type == "int")
{
return ParseNumeric<s32, 1>(asset_id, json_value, code_name, value);
}
else if (type == "int2")
{
return ParseNumeric<s32, 2>(asset_id, json_value, code_name, value);
}
else if (type == "int3")
{
return ParseNumeric<s32, 3>(asset_id, json_value, code_name, value);
}
else if (type == "int4")
{
return ParseNumeric<s32, 4>(asset_id, json_value, code_name, value);
}
else if (type == "float")
{
return ParseNumeric<float, 1>(asset_id, json_value, code_name, value);
}
else if (type == "float2")
{
return ParseNumeric<float, 2>(asset_id, json_value, code_name, value);
}
else if (type == "float3")
{
return ParseNumeric<float, 3>(asset_id, json_value, code_name, value);
}
else if (type == "float4")
{
return ParseNumeric<float, 4>(asset_id, json_value, code_name, value);
}
else if (type == "rgb")
{
ShaderProperty::RGB rgb;
if (!ParseNumeric<float, 3>(asset_id, json_value, code_name, &rgb.value))
return false;
*value = rgb;
return true;
}
else if (type == "rgba")
{
ShaderProperty::RGBA rgba;
if (!ParseNumeric<float, 4>(asset_id, json_value, code_name, &rgba.value))
return false;
*value = rgba;
return true;
}
else if (type == "bool")
{
if (json_value.is<bool>())
{
*value = json_value.get<bool>();
return true;
}
}
ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse the json, value is not valid for type '{}'",
asset_id, type);
return false;
}
static bool ParseShaderProperties(const VideoCommon::CustomAssetLibrary::AssetID& asset_id,
const picojson::array& properties_data,
std::vector<ShaderProperty>* shader_properties)
{
if (!shader_properties) [[unlikely]]
return false;
for (const auto& property_data : properties_data)
{
VideoCommon::ShaderProperty property;
if (!property_data.is<picojson::object>())
{
ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, property is not the right json type",
asset_id);
return false;
}
const auto& property_data_obj = property_data.get<picojson::object>();
const auto type_iter = property_data_obj.find("type");
if (type_iter == property_data_obj.end())
{
ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, property entry 'type' not found",
asset_id);
return false;
}
if (!type_iter->second.is<std::string>())
{
ERROR_LOG_FMT(VIDEO,
"Asset '{}' failed to parse json, property entry 'type' is not "
"the right json type",
asset_id);
return false;
}
std::string type = type_iter->second.to_str();
Common::ToLower(&type);
const auto description_iter = property_data_obj.find("description");
if (description_iter == property_data_obj.end())
{
ERROR_LOG_FMT(VIDEO,
"Asset '{}' failed to parse json, property entry 'description' not found",
asset_id);
return false;
}
if (!description_iter->second.is<std::string>())
{
ERROR_LOG_FMT(VIDEO,
"Asset '{}' failed to parse json, property entry 'description' is not "
"the right json type",
asset_id);
return false;
}
property.description = description_iter->second.to_str();
const auto code_name_iter = property_data_obj.find("code_name");
if (code_name_iter == property_data_obj.end())
{
ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, property entry 'code_name' not found",
asset_id);
return false;
}
if (!code_name_iter->second.is<std::string>())
{
ERROR_LOG_FMT(VIDEO,
"Asset '{}' failed to parse json, property entry 'code_name' is not "
"the right json type",
asset_id);
return false;
}
property.name = code_name_iter->second.to_str();
const auto default_iter = property_data_obj.find("default");
if (default_iter != property_data_obj.end())
{
if (!ParseShaderValue(asset_id, default_iter->second, property.name, type,
&property.default_value))
{
return false;
}
}
else
{
property.default_value = ShaderProperty::GetDefaultValueFromTypeName(type);
}
shader_properties->push_back(std::move(property));
}
return true;
}
bool RasterSurfaceShaderData::FromJson(const VideoCommon::CustomAssetLibrary::AssetID& asset_id,
const picojson::object& json, RasterSurfaceShaderData* data)
{
const auto parse_properties = [&](const char* name, std::string_view source,
std::vector<ShaderProperty>* shader_properties) -> bool {
const auto properties_iter = json.find(name);
if (properties_iter == json.end())
{
ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, '{}' not found", asset_id, name);
return false;
}
if (!properties_iter->second.is<picojson::array>())
{
ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, '{}' is not the right json type",
asset_id, name);
return false;
}
const auto& properties_array = properties_iter->second.get<picojson::array>();
return ParseShaderProperties(asset_id, properties_array, shader_properties);
};
const auto parse_samplers =
[&](const char* name,
std::vector<VideoCommon::RasterSurfaceShaderData::SamplerData>* samplers_out) -> bool {
const auto samplers_iter = json.find(name);
if (samplers_iter == json.end())
{
ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, '{}' not found", asset_id, name);
return false;
}
if (!samplers_iter->second.is<picojson::array>())
{
ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, '{}' is not the right json type",
asset_id, name);
return false;
}
const auto& samplers_array = samplers_iter->second.get<picojson::array>();
if (!std::ranges::all_of(samplers_array, [](const picojson::value& json_data) {
return json_data.is<picojson::object>();
}))
{
ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, '{}' must contain objects", asset_id,
name);
return false;
}
for (const auto& sampler_json : samplers_array)
{
auto& sampler_json_obj = sampler_json.get<picojson::object>();
SamplerData sampler;
if (const auto sampler_name = ReadStringFromJson(sampler_json_obj, "name"))
{
sampler.name = *sampler_name;
}
else
{
ERROR_LOG_FMT(VIDEO,
"Asset '{}' failed to parse sampler json, 'name' not found or wrong type",
asset_id);
return false;
}
if (const auto sampler_type =
ReadNumericFromJson<AbstractTextureType>(sampler_json_obj, "type"))
{
sampler.type = *sampler_type;
}
else
{
ERROR_LOG_FMT(VIDEO,
"Asset '{}' failed to parse sampler json, 'type' not found or wrong type",
asset_id);
return false;
}
samplers_out->push_back(std::move(sampler));
}
return true;
};
if (!parse_properties("properties", data->pixel_source, &data->uniform_properties))
return false;
if (!parse_samplers("samplers", &data->samplers))
return false;
return true;
}
void RasterSurfaceShaderData::ToJson(picojson::object& obj, const RasterSurfaceShaderData& data)
{
const auto add_property = [](picojson::array* json_properties, const ShaderProperty& property) {
picojson::object json_property;
json_property.emplace("code_name", property.name);
json_property.emplace("description", property.description);
std::visit(overloaded{[&](s32 default_value) {
json_property.emplace("type", "int");
json_property.emplace("default", static_cast<double>(default_value));
},
[&](const std::array<s32, 2>& default_value) {
json_property.emplace("type", "int2");
json_property.emplace("default", ToJsonArray(default_value));
},
[&](const std::array<s32, 3>& default_value) {
json_property.emplace("type", "int3");
json_property.emplace("default", ToJsonArray(default_value));
},
[&](const std::array<s32, 4>& default_value) {
json_property.emplace("type", "int4");
json_property.emplace("default", ToJsonArray(default_value));
},
[&](float default_value) {
json_property.emplace("type", "float");
json_property.emplace("default", static_cast<double>(default_value));
},
[&](const std::array<float, 2>& default_value) {
json_property.emplace("type", "float2");
json_property.emplace("default", ToJsonArray(default_value));
},
[&](const std::array<float, 3>& default_value) {
json_property.emplace("type", "float3");
json_property.emplace("default", ToJsonArray(default_value));
},
[&](const std::array<float, 4>& default_value) {
json_property.emplace("type", "float4");
json_property.emplace("default", ToJsonArray(default_value));
},
[&](const ShaderProperty::RGB& default_value) {
json_property.emplace("type", "rgb");
json_property.emplace("default", ToJsonArray(default_value.value));
},
[&](const ShaderProperty::RGBA& default_value) {
json_property.emplace("type", "rgba");
json_property.emplace("default", ToJsonArray(default_value.value));
},
[&](bool default_value) {
json_property.emplace("type", "bool");
json_property.emplace("default", default_value);
}},
property.default_value);
json_properties->emplace_back(std::move(json_property));
};
const auto add_sampler = [](picojson::array* json_samplers, const SamplerData& sampler) {
picojson::object json_sampler;
json_sampler.emplace("name", sampler.name);
json_sampler.emplace("type", static_cast<double>(sampler.type));
json_samplers->emplace_back(std::move(json_sampler));
};
picojson::array json_properties;
for (const auto& property : data.uniform_properties)
{
add_property(&json_properties, property);
}
obj.emplace("properties", std::move(json_properties));
picojson::array json_samplers;
for (const auto& sampler : data.samplers)
{
add_sampler(&json_samplers, sampler);
}
obj.emplace("samplers", json_samplers);
}
std::span<const std::string_view> ShaderProperty::GetValueTypeNames()
{
static constexpr std::array<std::string_view, 11> values = {
"int", "int2", "int3", "int4", "float", "float2", "float3", "float4", "rgb", "rgba", "bool"};
return values;
}
ShaderProperty::Value ShaderProperty::GetDefaultValueFromTypeName(std::string_view name)
{
if (name == "int")
{
return 0;
}
else if (name == "int2")
{
return std::array<s32, 2>{};
}
else if (name == "int3")
{
return std::array<s32, 3>{};
}
else if (name == "int4")
{
return std::array<s32, 4>{};
}
else if (name == "float")
{
return 0.0f;
}
else if (name == "float2")
{
return std::array<float, 2>{};
}
else if (name == "float3")
{
return std::array<float, 3>{};
}
else if (name == "float4")
{
return std::array<float, 4>{};
}
else if (name == "rgb")
{
return RGB{};
}
else if (name == "rgba")
{
return RGBA{};
}
else if (name == "bool")
{
return false;
}
return Value{};
}
void ShaderProperty::WriteAsShaderCode(ShaderCode& shader_source, const ShaderProperty& property)
{
const auto write_shader = [&](std::string_view type, u32 element_count) {
if (element_count == 1)
{
shader_source.Write("{} {};\n", type, property.name);
}
else
{
shader_source.Write("{}{} {};\n", type, element_count, property.name);
}
for (std::size_t i = element_count; i < 4; i++)
{
shader_source.Write("{} {}_padding_{};\n", type, property.name, i + 1);
}
};
std::visit(overloaded{[&](s32) { write_shader("int", 1); },
[&](const std::array<s32, 2>&) { write_shader("int", 2); },
[&](const std::array<s32, 3>&) { write_shader("int", 3); },
[&](const std::array<s32, 4>&) { write_shader("int", 4); },
[&](float) { write_shader("float", 1); },
[&](const std::array<float, 2>&) { write_shader("float", 2); },
[&](const std::array<float, 3>&) { write_shader("float", 3); },
[&](const std::array<float, 4>&) { write_shader("float", 4); },
[&](const ShaderProperty::RGB&) { write_shader("float", 3); },
[&](const ShaderProperty::RGBA&) { write_shader("float", 4); },
[&](bool) { write_shader("bool", 1); }},
property.default_value);
}
CustomAssetLibrary::LoadInfo
RasterSurfaceShaderAsset::LoadImpl(const CustomAssetLibrary::AssetID& asset_id)
{
auto potential_data = std::make_shared<RasterSurfaceShaderData>();
const auto loaded_info =
m_owning_library->LoadRasterSurfaceShader(asset_id, potential_data.get());
if (loaded_info.bytes_loaded == 0)
return {};
{
std::lock_guard lk(m_data_lock);
m_loaded = true;
m_data = std::move(potential_data);
}
return loaded_info;
}
} // namespace VideoCommon
@@ -1,89 +0,0 @@
// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <memory>
#include <span>
#include <string>
#include <string_view>
#include <variant>
#include <vector>
#include <picojson.h>
#include "VideoCommon/Assets/CustomAsset.h"
#include "VideoCommon/ShaderCompileUtils.h"
#include "VideoCommon/TextureConfig.h"
class ShaderCode;
namespace VideoCommon
{
struct ShaderProperty
{
struct RGB
{
std::array<float, 3> value;
};
struct RGBA
{
std::array<float, 4> value;
};
using Value = std::variant<s32, std::array<s32, 2>, std::array<s32, 3>, std::array<s32, 4>, float,
std::array<float, 2>, std::array<float, 3>, std::array<float, 4>, bool,
RGB, RGBA>;
static std::span<const std::string_view> GetValueTypeNames();
static Value GetDefaultValueFromTypeName(std::string_view name);
static void WriteAsShaderCode(ShaderCode& shader_source, const ShaderProperty& property);
Value default_value;
std::string name;
std::string description;
};
struct RasterSurfaceShaderData
{
RasterSurfaceShaderData() = default;
RasterSurfaceShaderData(const RasterSurfaceShaderData&) = delete;
RasterSurfaceShaderData(RasterSurfaceShaderData&&) = default;
~RasterSurfaceShaderData() = default;
RasterSurfaceShaderData& operator=(const RasterSurfaceShaderData&) = delete;
RasterSurfaceShaderData& operator=(RasterSurfaceShaderData&&) = default;
static bool FromJson(const CustomAssetLibrary::AssetID& asset_id, const picojson::object& json,
RasterSurfaceShaderData* data);
static void ToJson(picojson::object& obj, const RasterSurfaceShaderData& data);
// These shader properties describe the input that the
// shader expects to expose. The key is text
// expected to be in the shader code and the property
// describes various details about the input
std::vector<ShaderProperty> uniform_properties;
std::string vertex_source;
std::string pixel_source;
struct SamplerData
{
AbstractTextureType type;
std::string name;
bool operator==(const SamplerData&) const = default;
};
std::vector<SamplerData> samplers;
std::unique_ptr<VideoCommon::ShaderIncluder> shader_includer;
};
class RasterSurfaceShaderAsset final : public CustomLoadableAsset<RasterSurfaceShaderData>
{
public:
using CustomLoadableAsset::CustomLoadableAsset;
private:
CustomAssetLibrary::LoadInfo LoadImpl(const CustomAssetLibrary::AssetID& asset_id) override;
};
} // namespace VideoCommon
@@ -3,262 +3,10 @@
#include "VideoCommon/Assets/TextureAsset.h"
#include <optional>
#include "Common/JsonUtil.h"
#include "Common/Logging/Log.h"
#include "Common/StringUtil.h"
#include "VideoCommon/BPMemory.h"
namespace VideoCommon
{
namespace
{
std::optional<WrapMode> ReadWrapModeFromJSON(const picojson::object& json, const std::string& uv)
{
auto uv_mode = ReadStringFromJson(json, uv).value_or("");
Common::ToLower(&uv_mode);
if (uv_mode == "clamp")
{
return WrapMode::Clamp;
}
else if (uv_mode == "repeat")
{
return WrapMode::Repeat;
}
else if (uv_mode == "mirror")
{
return WrapMode::Mirror;
}
return std::nullopt;
}
std::optional<FilterMode> ReadFilterModeFromJSON(const picojson::object& json,
const std::string& filter)
{
auto filter_mode = ReadStringFromJson(json, filter).value_or("");
Common::ToLower(&filter_mode);
if (filter_mode == "linear")
{
return FilterMode::Linear;
}
else if (filter_mode == "near")
{
return FilterMode::Near;
}
return std::nullopt;
}
bool ParseSampler(const VideoCommon::CustomAssetLibrary::AssetID& asset_id,
const picojson::object& json, SamplerState* sampler)
{
if (!sampler) [[unlikely]]
return false;
*sampler = RenderState::GetLinearSamplerState();
const auto sampler_state_wrap_iter = json.find("wrap_mode");
if (sampler_state_wrap_iter != json.end())
{
if (!sampler_state_wrap_iter->second.is<picojson::object>())
{
ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, 'wrap_mode' is not the right type",
asset_id);
return false;
}
const auto sampler_state_wrap_obj = sampler_state_wrap_iter->second.get<picojson::object>();
if (const auto mode = ReadWrapModeFromJSON(sampler_state_wrap_obj, "u"))
{
sampler->tm0.wrap_u = *mode;
}
else
{
ERROR_LOG_FMT(VIDEO,
"Asset '{}' failed to parse json, 'wrap_mode[u]' has an invalid "
"value",
asset_id);
return false;
}
if (const auto mode = ReadWrapModeFromJSON(sampler_state_wrap_obj, "v"))
{
sampler->tm0.wrap_v = *mode;
}
else
{
ERROR_LOG_FMT(VIDEO,
"Asset '{}' failed to parse json, 'wrap_mode[v]' has an invalid "
"value",
asset_id);
return false;
}
}
const auto sampler_state_filter_iter = json.find("filter_mode");
if (sampler_state_filter_iter != json.end())
{
if (!sampler_state_filter_iter->second.is<picojson::object>())
{
ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, 'filter_mode' is not the right type",
asset_id);
return false;
}
const auto sampler_state_filter_obj = sampler_state_filter_iter->second.get<picojson::object>();
if (const auto mode = ReadFilterModeFromJSON(sampler_state_filter_obj, "min"))
{
sampler->tm0.min_filter = *mode;
}
else
{
ERROR_LOG_FMT(VIDEO,
"Asset '{}' failed to parse json, 'filter_mode[min]' has an invalid "
"value",
asset_id);
return false;
}
if (const auto mode = ReadFilterModeFromJSON(sampler_state_filter_obj, "mag"))
{
sampler->tm0.mag_filter = *mode;
}
else
{
ERROR_LOG_FMT(VIDEO,
"Asset '{}' failed to parse json, 'filter_mode[mag]' has an invalid "
"value",
asset_id);
return false;
}
if (const auto mode = ReadFilterModeFromJSON(sampler_state_filter_obj, "mipmap"))
{
sampler->tm0.mipmap_filter = *mode;
}
else
{
ERROR_LOG_FMT(VIDEO,
"Asset '{}' failed to parse json, 'filter_mode[mipmap]' has an invalid "
"value",
asset_id);
return false;
}
}
return true;
}
} // namespace
bool TextureAndSamplerData::FromJson(const CustomAssetLibrary::AssetID& asset_id,
const picojson::object& json, TextureAndSamplerData* data)
{
const auto type_iter = json.find("type");
if (type_iter == json.end())
{
ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, property entry 'type' not found",
asset_id);
return false;
}
if (!type_iter->second.is<std::string>())
{
ERROR_LOG_FMT(VIDEO,
"Asset '{}' failed to parse json, property entry 'type' is not "
"the right json type",
asset_id);
return false;
}
std::string type = type_iter->second.to_str();
Common::ToLower(&type);
if (type == "texture2d")
{
data->type = AbstractTextureType::Texture_2D;
if (!ParseSampler(asset_id, json, &data->sampler))
{
return false;
}
}
else if (type == "texturecube")
{
data->type = AbstractTextureType::Texture_CubeMap;
}
else if (type == "texture2darray")
{
data->type = AbstractTextureType::Texture_2DArray;
}
else
{
ERROR_LOG_FMT(VIDEO,
"Asset '{}' failed to parse json, texture type '{}' "
"an invalid option",
asset_id, type);
return false;
}
return true;
}
void TextureAndSamplerData::ToJson(picojson::object* obj, const TextureAndSamplerData& data)
{
if (!obj) [[unlikely]]
return;
auto& json_obj = *obj;
switch (data.type)
{
case AbstractTextureType::Texture_2D:
json_obj.emplace("type", "texture2d");
break;
case AbstractTextureType::Texture_CubeMap:
json_obj.emplace("type", "texturecube");
break;
case AbstractTextureType::Texture_2DArray:
json_obj.emplace("type", "texture2darray");
break;
};
auto wrap_mode_to_string = [](WrapMode mode) {
switch (mode)
{
case WrapMode::Clamp:
return "clamp";
case WrapMode::Mirror:
return "mirror";
case WrapMode::Repeat:
return "repeat";
};
return "";
};
auto filter_mode_to_string = [](FilterMode mode) {
switch (mode)
{
case FilterMode::Linear:
return "linear";
case FilterMode::Near:
return "near";
};
return "";
};
picojson::object wrap_mode;
wrap_mode.emplace("u", wrap_mode_to_string(data.sampler.tm0.wrap_u));
wrap_mode.emplace("v", wrap_mode_to_string(data.sampler.tm0.wrap_v));
json_obj.emplace("wrap_mode", wrap_mode);
picojson::object filter_mode;
filter_mode.emplace("min", filter_mode_to_string(data.sampler.tm0.min_filter));
filter_mode.emplace("mag", filter_mode_to_string(data.sampler.tm0.mag_filter));
filter_mode.emplace("mipmap", filter_mode_to_string(data.sampler.tm0.mipmap_filter));
json_obj.emplace("filter_mode", filter_mode);
}
CustomAssetLibrary::LoadInfo TextureAsset::LoadImpl(const CustomAssetLibrary::AssetID& asset_id)
{
auto potential_data = std::make_shared<CustomTextureData>();
@@ -272,19 +20,4 @@ CustomAssetLibrary::LoadInfo TextureAsset::LoadImpl(const CustomAssetLibrary::As
}
return loaded_info;
}
CustomAssetLibrary::LoadInfo
TextureAndSamplerAsset::LoadImpl(const CustomAssetLibrary::AssetID& asset_id)
{
auto potential_data = std::make_shared<TextureAndSamplerData>();
const auto loaded_info = m_owning_library->LoadTexture(asset_id, potential_data.get());
if (loaded_info.bytes_loaded == 0)
return {};
{
std::lock_guard lk(m_data_lock);
m_loaded = true;
m_data = std::move(potential_data);
}
return loaded_info;
}
} // namespace VideoCommon
@@ -3,40 +3,16 @@
#pragma once
#include <fmt/format.h>
#include <picojson.h>
#include "Common/EnumFormatter.h"
#include "VideoCommon/Assets/CustomAsset.h"
#include "VideoCommon/Assets/CustomTextureData.h"
#include "VideoCommon/RenderState.h"
namespace VideoCommon
{
struct TextureAndSamplerData
{
static bool FromJson(const CustomAssetLibrary::AssetID& asset_id, const picojson::object& json,
TextureAndSamplerData* data);
static void ToJson(picojson::object* obj, const TextureAndSamplerData& data);
AbstractTextureType type;
CustomTextureData texture_data;
SamplerState sampler;
};
class TextureAsset final : public CustomLoadableAsset<CustomTextureData>
{
public:
using CustomLoadableAsset::CustomLoadableAsset;
private:
CustomAssetLibrary::LoadInfo LoadImpl(const CustomAssetLibrary::AssetID& asset_id) override;
};
class TextureAndSamplerAsset final : public CustomLoadableAsset<TextureAndSamplerData>
{
public:
using CustomLoadableAsset::CustomLoadableAsset;
private:
CustomAssetLibrary::LoadInfo LoadImpl(const CustomAssetLibrary::AssetID& asset_id) override;
};
@@ -1,60 +0,0 @@
// Copyright 2024 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "VideoCommon/Assets/TextureSamplerValue.h"
#include <optional>
#include "Common/JsonUtil.h"
#include "Common/StringUtil.h"
namespace VideoCommon
{
namespace
{
std::optional<TextureSamplerValue::SamplerOrigin>
ReadSamplerOriginFromJSON(const picojson::object& json)
{
auto sampler_origin = ReadStringFromJson(json, "sampler_origin").value_or("");
Common::ToLower(&sampler_origin);
if (sampler_origin == "asset")
{
return TextureSamplerValue::SamplerOrigin::Asset;
}
else if (sampler_origin == "texture_hash")
{
return TextureSamplerValue::SamplerOrigin::TextureHash;
}
return std::nullopt;
}
} // namespace
std::string TextureSamplerValue::ToString(SamplerOrigin sampler_origin)
{
if (sampler_origin == SamplerOrigin::Asset)
return "asset";
return "texture_hash";
}
bool TextureSamplerValue::FromJson(const picojson::object& json, TextureSamplerValue* data)
{
data->asset = ReadStringFromJson(json, "asset").value_or("");
data->texture_hash = ReadStringFromJson(json, "texture_hash").value_or("");
data->sampler_origin =
ReadSamplerOriginFromJSON(json).value_or(TextureSamplerValue::SamplerOrigin::Asset);
return true;
}
void TextureSamplerValue::ToJson(picojson::object* obj, const TextureSamplerValue& data)
{
if (!obj) [[unlikely]]
return;
obj->emplace("asset", data.asset);
obj->emplace("texture_hash", data.texture_hash);
obj->emplace("sampler_origin", ToString(data.sampler_origin));
}
} // namespace VideoCommon
Loaded 100 of 142 files, more files were not shown because too many files have changed in this diff. Show more