PostFX analític: PostFXParams/Preset amb chroma_min/max + scan_*, elimina supersampling
This commit is contained in:
@@ -9,358 +9,15 @@
|
||||
|
||||
#ifndef __APPLE__
|
||||
#include "core/rendering/sdl3gpu/spv/crtpi_frag_spv.h"
|
||||
#include "core/rendering/sdl3gpu/spv/downscale_frag_spv.h"
|
||||
#include "core/rendering/sdl3gpu/spv/postfx_frag_spv.h"
|
||||
#include "core/rendering/sdl3gpu/spv/postfx_vert_spv.h"
|
||||
#include "core/rendering/sdl3gpu/spv/upscale_frag_spv.h"
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
// ============================================================================
|
||||
// MSL shaders (Metal Shading Language) — macOS
|
||||
// ============================================================================
|
||||
|
||||
static const char* POSTFX_VERT_MSL = R"(
|
||||
#include <metal_stdlib>
|
||||
using namespace metal;
|
||||
|
||||
struct PostVOut {
|
||||
float4 pos [[position]];
|
||||
float2 uv;
|
||||
};
|
||||
|
||||
vertex PostVOut postfx_vs(uint vid [[vertex_id]]) {
|
||||
const float2 positions[3] = { {-1.0, -1.0}, {3.0, -1.0}, {-1.0, 3.0} };
|
||||
const float2 uvs[3] = { { 0.0, 1.0}, {2.0, 1.0}, { 0.0,-1.0} };
|
||||
PostVOut out;
|
||||
out.pos = float4(positions[vid], 0.0, 1.0);
|
||||
out.uv = uvs[vid];
|
||||
return out;
|
||||
}
|
||||
)";
|
||||
|
||||
static const char* POSTFX_FRAG_MSL = R"(
|
||||
#include <metal_stdlib>
|
||||
using namespace metal;
|
||||
|
||||
struct PostVOut {
|
||||
float4 pos [[position]];
|
||||
float2 uv;
|
||||
};
|
||||
|
||||
struct PostFXUniforms {
|
||||
float vignette_strength;
|
||||
float chroma_strength;
|
||||
float scanline_strength;
|
||||
float screen_height;
|
||||
float mask_strength;
|
||||
float gamma_strength;
|
||||
float curvature;
|
||||
float bleeding;
|
||||
float pixel_scale;
|
||||
float time;
|
||||
float oversample; // 1.0 = sin SS, 3.0 = 3× supersampling
|
||||
float flicker; // 0 = off, 1 = phosphor flicker ~50 Hz
|
||||
};
|
||||
|
||||
// YCbCr helpers for NTSC bleeding
|
||||
static float3 rgb_to_ycc(float3 rgb) {
|
||||
return float3(
|
||||
0.299f*rgb.r + 0.587f*rgb.g + 0.114f*rgb.b,
|
||||
-0.169f*rgb.r - 0.331f*rgb.g + 0.500f*rgb.b + 0.5f,
|
||||
0.500f*rgb.r - 0.419f*rgb.g - 0.081f*rgb.b + 0.5f
|
||||
);
|
||||
}
|
||||
static float3 ycc_to_rgb(float3 ycc) {
|
||||
float y = ycc.x;
|
||||
float cb = ycc.y - 0.5f;
|
||||
float cr = ycc.z - 0.5f;
|
||||
return clamp(float3(
|
||||
y + 1.402f*cr,
|
||||
y - 0.344f*cb - 0.714f*cr,
|
||||
y + 1.772f*cb
|
||||
), 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
fragment float4 postfx_fs(PostVOut in [[stage_in]],
|
||||
texture2d<float> scene [[texture(0)]],
|
||||
sampler samp [[sampler(0)]],
|
||||
constant PostFXUniforms& u [[buffer(0)]]) {
|
||||
float2 uv = in.uv;
|
||||
|
||||
// Curvatura barrel CRT
|
||||
if (u.curvature > 0.0f) {
|
||||
float2 c = uv - 0.5f;
|
||||
float rsq = dot(c, c);
|
||||
float2 dist = float2(0.05f, 0.1f) * u.curvature;
|
||||
float2 barrelScale = 1.0f - 0.23f * dist;
|
||||
c += c * (dist * rsq);
|
||||
c *= barrelScale;
|
||||
if (abs(c.x) >= 0.5f || abs(c.y) >= 0.5f) {
|
||||
return float4(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
}
|
||||
uv = c + 0.5f;
|
||||
}
|
||||
|
||||
// Muestra base
|
||||
float3 base = scene.sample(samp, uv).rgb;
|
||||
|
||||
// Sangrado NTSC — difuminado horizontal de crominancia.
|
||||
// step = 1 pixel de juego en espacio UV (corrige SS: scene.get_width() = game_w * oversample).
|
||||
float3 colour;
|
||||
if (u.bleeding > 0.0f) {
|
||||
float tw = float(scene.get_width());
|
||||
float step = u.oversample / tw; // 1 pixel lógico en UV
|
||||
float3 ycc = rgb_to_ycc(base);
|
||||
float3 ycc_l2 = rgb_to_ycc(scene.sample(samp, uv - float2(2.0f*step, 0.0f)).rgb);
|
||||
float3 ycc_l1 = rgb_to_ycc(scene.sample(samp, uv - float2(1.0f*step, 0.0f)).rgb);
|
||||
float3 ycc_r1 = rgb_to_ycc(scene.sample(samp, uv + float2(1.0f*step, 0.0f)).rgb);
|
||||
float3 ycc_r2 = rgb_to_ycc(scene.sample(samp, uv + float2(2.0f*step, 0.0f)).rgb);
|
||||
ycc.yz = (ycc_l2.yz + ycc_l1.yz*2.0f + ycc.yz*2.0f + ycc_r1.yz*2.0f + ycc_r2.yz) / 8.0f;
|
||||
colour = mix(base, ycc_to_rgb(ycc), u.bleeding);
|
||||
} else {
|
||||
colour = base;
|
||||
}
|
||||
|
||||
// Aberración cromática (drift animado con time para efecto NTSC real)
|
||||
float ca = u.chroma_strength * 0.005f * (1.0f + 0.15f * sin(u.time * 7.3f));
|
||||
colour.r = scene.sample(samp, uv + float2(ca, 0.0f)).r;
|
||||
colour.b = scene.sample(samp, uv - float2(ca, 0.0f)).b;
|
||||
|
||||
// Corrección gamma (linealizar antes de scanlines, codificar después)
|
||||
if (u.gamma_strength > 0.0f) {
|
||||
float3 lin = pow(colour, float3(2.4f));
|
||||
colour = mix(colour, lin, u.gamma_strength);
|
||||
}
|
||||
|
||||
// Scanlines — proporción 2/3 brillantes + 1/3 oscuras por fila lógica.
|
||||
// Casos especiales: 1 subfila → sin efecto; 2 subfilas → 1+1 (50/50).
|
||||
// Constantes ajustables:
|
||||
const float SCAN_DARK_RATIO = 0.333f; // fracción de subfilas oscuras (ps >= 3)
|
||||
const float SCAN_DARK_FLOOR = 0.42f; // multiplicador de brillo de subfilas oscuras
|
||||
if (u.scanline_strength > 0.0f) {
|
||||
float ps = max(1.0f, round(u.pixel_scale));
|
||||
float frac_in_row = fract(uv.y * u.screen_height);
|
||||
float row_pos = floor(frac_in_row * ps);
|
||||
float bright_rows = (ps < 2.0f) ? ps : ((ps < 3.0f) ? 1.0f : floor(ps * (1.0f - SCAN_DARK_RATIO)));
|
||||
float is_dark = step(bright_rows, row_pos);
|
||||
float scan = mix(1.0f, SCAN_DARK_FLOOR, is_dark);
|
||||
colour *= mix(1.0f, scan, u.scanline_strength);
|
||||
}
|
||||
|
||||
if (u.gamma_strength > 0.0f) {
|
||||
float3 enc = pow(colour, float3(1.0f/2.2f));
|
||||
colour = mix(colour, enc, u.gamma_strength);
|
||||
}
|
||||
|
||||
// Viñeta
|
||||
float2 d = uv - 0.5f;
|
||||
float vignette = 1.0f - dot(d, d) * u.vignette_strength;
|
||||
colour *= clamp(vignette, 0.0f, 1.0f);
|
||||
|
||||
// Máscara de fósforo RGB — después de scanlines (orden original):
|
||||
// filas brillantes saturadas → máscara invisible, filas oscuras → RGB visible.
|
||||
if (u.mask_strength > 0.0f) {
|
||||
float whichMask = fract(in.pos.x * 0.3333333f);
|
||||
float3 mask = float3(0.80f);
|
||||
if (whichMask < 0.3333333f) mask.x = 1.0f;
|
||||
else if (whichMask < 0.6666667f) mask.y = 1.0f;
|
||||
else mask.z = 1.0f;
|
||||
colour = mix(colour, colour * mask, u.mask_strength);
|
||||
}
|
||||
|
||||
// Parpadeo de fósforo CRT (~50 Hz)
|
||||
if (u.flicker > 0.0f) {
|
||||
float flicker_wave = sin(u.time * 100.0f) * 0.5f + 0.5f;
|
||||
colour *= 1.0f - u.flicker * 0.04f * flicker_wave;
|
||||
}
|
||||
|
||||
return float4(colour, 1.0f);
|
||||
}
|
||||
)";
|
||||
static const char* UPSCALE_FRAG_MSL = R"(
|
||||
#include <metal_stdlib>
|
||||
using namespace metal;
|
||||
struct VertOut { float4 pos [[position]]; float2 uv; };
|
||||
fragment float4 upscale_fs(VertOut in [[stage_in]],
|
||||
texture2d<float> scene [[texture(0)]],
|
||||
sampler smp [[sampler(0)]])
|
||||
{
|
||||
return scene.sample(smp, in.uv);
|
||||
}
|
||||
)";
|
||||
|
||||
static const char* DOWNSCALE_FRAG_MSL = R"(
|
||||
#include <metal_stdlib>
|
||||
using namespace metal;
|
||||
struct VertOut { float4 pos [[position]]; float2 uv; };
|
||||
struct DownscaleUniforms { int algorithm; float pad0; float pad1; float pad2; };
|
||||
|
||||
static float lanczos_w(float t, float a) {
|
||||
t = abs(t);
|
||||
if (t < 0.0001f) { return 1.0f; }
|
||||
if (t >= a) { return 0.0f; }
|
||||
const float PI = 3.14159265358979f;
|
||||
float pt = PI * t;
|
||||
return (a * sin(pt) * sin(pt / a)) / (pt * pt);
|
||||
}
|
||||
|
||||
fragment float4 downscale_fs(VertOut in [[stage_in]],
|
||||
texture2d<float> source [[texture(0)]],
|
||||
sampler smp [[sampler(0)]],
|
||||
constant DownscaleUniforms& u [[buffer(0)]])
|
||||
{
|
||||
float2 src_size = float2(source.get_width(), source.get_height());
|
||||
float2 p = in.uv * src_size;
|
||||
float2 p_floor = floor(p);
|
||||
float a = (u.algorithm == 0) ? 2.0f : 3.0f;
|
||||
int win = int(a);
|
||||
float4 color = float4(0.0f);
|
||||
float weight_sum = 0.0f;
|
||||
for (int j = -win; j <= win; j++) {
|
||||
for (int i = -win; i <= win; i++) {
|
||||
float2 tap_center = p_floor + float2(float(i), float(j)) + 0.5f;
|
||||
float2 offset = tap_center - p;
|
||||
float w = lanczos_w(offset.x, a) * lanczos_w(offset.y, a);
|
||||
color += source.sample(smp, tap_center / src_size) * w;
|
||||
weight_sum += w;
|
||||
}
|
||||
}
|
||||
return (weight_sum > 0.0f) ? (color / weight_sum) : float4(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
}
|
||||
)";
|
||||
static const char* CRTPI_FRAG_MSL = R"(
|
||||
#include <metal_stdlib>
|
||||
using namespace metal;
|
||||
|
||||
struct PostVOut {
|
||||
float4 pos [[position]];
|
||||
float2 uv;
|
||||
};
|
||||
|
||||
struct CrtPiUniforms {
|
||||
// vec4 #0
|
||||
float scanline_weight;
|
||||
float scanline_gap_brightness;
|
||||
float bloom_factor;
|
||||
float input_gamma;
|
||||
// vec4 #1
|
||||
float output_gamma;
|
||||
float mask_brightness;
|
||||
float curvature_x;
|
||||
float curvature_y;
|
||||
// vec4 #2
|
||||
int mask_type;
|
||||
int enable_scanlines;
|
||||
int enable_multisample;
|
||||
int enable_gamma;
|
||||
// vec4 #3
|
||||
int enable_curvature;
|
||||
int enable_sharper;
|
||||
float texture_width;
|
||||
float texture_height;
|
||||
};
|
||||
|
||||
static float2 crtpi_distort(float2 coord, float2 screen_scale, float cx, float cy) {
|
||||
float2 curvature = float2(cx, cy);
|
||||
float2 barrel_scale = 1.0f - (0.23f * curvature);
|
||||
coord *= screen_scale;
|
||||
coord -= 0.5f;
|
||||
float rsq = coord.x * coord.x + coord.y * coord.y;
|
||||
coord += coord * (curvature * rsq);
|
||||
coord *= barrel_scale;
|
||||
if (abs(coord.x) >= 0.5f || abs(coord.y) >= 0.5f) { return float2(-1.0f); }
|
||||
coord += 0.5f;
|
||||
coord /= screen_scale;
|
||||
return coord;
|
||||
}
|
||||
|
||||
static float crtpi_scan_weight(float dist, float sw, float gap) {
|
||||
return max(1.0f - dist * dist * sw, gap);
|
||||
}
|
||||
|
||||
static float crtpi_scan_line(float dy, float filter_w, float sw, float gap, bool ms) {
|
||||
float w = crtpi_scan_weight(dy, sw, gap);
|
||||
if (ms) {
|
||||
w += crtpi_scan_weight(dy - filter_w, sw, gap);
|
||||
w += crtpi_scan_weight(dy + filter_w, sw, gap);
|
||||
w *= 0.3333333f;
|
||||
}
|
||||
return w;
|
||||
}
|
||||
|
||||
fragment float4 crtpi_fs(PostVOut in [[stage_in]],
|
||||
texture2d<float> tex [[texture(0)]],
|
||||
sampler samp [[sampler(0)]],
|
||||
constant CrtPiUniforms& u [[buffer(0)]]) {
|
||||
float2 tex_size = float2(u.texture_width, u.texture_height);
|
||||
float filter_width = (768.0f / u.texture_height) / 3.0f;
|
||||
float2 texcoord = in.uv;
|
||||
|
||||
if (u.enable_curvature != 0) {
|
||||
texcoord = crtpi_distort(texcoord, float2(1.0f, 1.0f), u.curvature_x, u.curvature_y);
|
||||
if (texcoord.x < 0.0f) { return float4(0.0f, 0.0f, 0.0f, 1.0f); }
|
||||
}
|
||||
|
||||
float2 coord_in_pixels = texcoord * tex_size;
|
||||
float2 tc;
|
||||
float scan_weight;
|
||||
|
||||
if (u.enable_sharper != 0) {
|
||||
float2 temp = floor(coord_in_pixels) + 0.5f;
|
||||
tc = temp / tex_size;
|
||||
float2 deltas = coord_in_pixels - temp;
|
||||
scan_weight = crtpi_scan_line(deltas.y, filter_width, u.scanline_weight, u.scanline_gap_brightness, u.enable_multisample != 0);
|
||||
float2 signs = sign(deltas);
|
||||
deltas.x *= 2.0f;
|
||||
deltas = deltas * deltas;
|
||||
deltas.y = deltas.y * deltas.y;
|
||||
deltas.x *= 0.5f;
|
||||
deltas.y *= 8.0f;
|
||||
deltas /= tex_size;
|
||||
deltas *= signs;
|
||||
tc = tc + deltas;
|
||||
} else {
|
||||
float temp_y = floor(coord_in_pixels.y) + 0.5f;
|
||||
float y_coord = temp_y / tex_size.y;
|
||||
float dy = coord_in_pixels.y - temp_y;
|
||||
scan_weight = crtpi_scan_line(dy, filter_width, u.scanline_weight, u.scanline_gap_brightness, u.enable_multisample != 0);
|
||||
float sign_y = sign(dy);
|
||||
dy = dy * dy;
|
||||
dy = dy * dy;
|
||||
dy *= 8.0f;
|
||||
dy /= tex_size.y;
|
||||
dy *= sign_y;
|
||||
tc = float2(texcoord.x, y_coord + dy);
|
||||
}
|
||||
|
||||
float3 colour = tex.sample(samp, tc).rgb;
|
||||
|
||||
if (u.enable_scanlines != 0) {
|
||||
if (u.enable_gamma != 0) { colour = pow(colour, float3(u.input_gamma)); }
|
||||
colour *= scan_weight * u.bloom_factor;
|
||||
if (u.enable_gamma != 0) { colour = pow(colour, float3(1.0f / u.output_gamma)); }
|
||||
}
|
||||
|
||||
if (u.mask_type == 1) {
|
||||
float wm = fract(in.pos.x * 0.5f);
|
||||
float3 mask = (wm < 0.5f) ? float3(u.mask_brightness, 1.0f, u.mask_brightness)
|
||||
: float3(1.0f, u.mask_brightness, 1.0f);
|
||||
colour *= mask;
|
||||
} else if (u.mask_type == 2) {
|
||||
float wm = fract(in.pos.x * 0.3333333f);
|
||||
float3 mask = float3(u.mask_brightness);
|
||||
if (wm < 0.3333333f) mask.x = 1.0f;
|
||||
else if (wm < 0.6666666f) mask.y = 1.0f;
|
||||
else mask.z = 1.0f;
|
||||
colour *= mask;
|
||||
}
|
||||
|
||||
return float4(colour, 1.0f);
|
||||
}
|
||||
)";
|
||||
|
||||
#endif // __APPLE__
|
||||
#include "core/rendering/sdl3gpu/msl/crtpi_frag.msl.h"
|
||||
#include "core/rendering/sdl3gpu/msl/postfx_frag.msl.h"
|
||||
#include "core/rendering/sdl3gpu/msl/postfx_vert.msl.h"
|
||||
#endif
|
||||
|
||||
namespace Rendering {
|
||||
|
||||
@@ -394,7 +51,6 @@ namespace Rendering {
|
||||
game_width_ = static_cast<int>(fw);
|
||||
game_height_ = static_cast<int>(fh);
|
||||
uniforms_.screen_height = static_cast<float>(game_height_);
|
||||
uniforms_.oversample = static_cast<float>(oversample_);
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// 1. Create GPU device (solo si no existe ya)
|
||||
@@ -454,9 +110,6 @@ namespace Rendering {
|
||||
return false;
|
||||
}
|
||||
|
||||
// scaled_texture_ se creará en el primer render() una vez conocido el zoom de ventana
|
||||
ss_factor_ = 0;
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// 4. Create upload transfer buffer (CPU → GPU, always game resolution)
|
||||
// ----------------------------------------------------------------
|
||||
@@ -471,7 +124,7 @@ namespace Rendering {
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// 5. Create samplers: NEAREST (pixel art) + LINEAR (supersampling)
|
||||
// 5. Create sampler: NEAREST (pixel art)
|
||||
// ----------------------------------------------------------------
|
||||
SDL_GPUSamplerCreateInfo samp_info = {};
|
||||
samp_info.min_filter = SDL_GPU_FILTER_NEAREST;
|
||||
@@ -487,20 +140,6 @@ namespace Rendering {
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_GPUSamplerCreateInfo lsamp_info = {};
|
||||
lsamp_info.min_filter = SDL_GPU_FILTER_LINEAR;
|
||||
lsamp_info.mag_filter = SDL_GPU_FILTER_LINEAR;
|
||||
lsamp_info.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_NEAREST;
|
||||
lsamp_info.address_mode_u = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
|
||||
lsamp_info.address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
|
||||
lsamp_info.address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
|
||||
linear_sampler_ = SDL_CreateGPUSampler(device_, &lsamp_info);
|
||||
if (linear_sampler_ == nullptr) {
|
||||
SDL_Log("SDL3GPUShader: failed to create linear sampler: %s", SDL_GetError());
|
||||
cleanup();
|
||||
return false;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// 6. Create PostFX graphics pipeline
|
||||
// ----------------------------------------------------------------
|
||||
@@ -518,7 +157,7 @@ namespace Rendering {
|
||||
}
|
||||
|
||||
is_initialized_ = true;
|
||||
std::cout << "GPU Shader : initialized OK — game " << game_width_ << 'x' << game_height_ << ", oversample " << oversample_ << '\n';
|
||||
std::cout << "GPU Shader : initialized OK — game " << game_width_ << 'x' << game_height_ << '\n';
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -527,7 +166,7 @@ namespace Rendering {
|
||||
// ---------------------------------------------------------------------------
|
||||
auto SDL3GPUShader::createPostfxVertexShader() -> SDL_GPUShader* {
|
||||
#ifdef __APPLE__
|
||||
return createShaderMSL(device_, POSTFX_VERT_MSL, "postfx_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
return createShaderMSL(device_, Msl::kPostfxVert, "postfx_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
#else
|
||||
return createShaderSPIRV(device_, kpostfx_vert_spv, kpostfx_vert_spv_size, "main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
#endif
|
||||
@@ -579,42 +218,29 @@ namespace Rendering {
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// createPipeline — crea els 4 pipelines del flux PostFX
|
||||
// createPipeline — crea el pipeline PostFX que va directament al swapchain
|
||||
// ---------------------------------------------------------------------------
|
||||
auto SDL3GPUShader::createPipeline() -> bool {
|
||||
const SDL_GPUTextureFormat SWAPCHAIN_FMT = SDL_GetGPUSwapchainTextureFormat(device_, window_);
|
||||
const SDL_GPUTextureFormat OFFSCREEN_FMT = SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM;
|
||||
|
||||
#ifdef __APPLE__
|
||||
SDL_GPUShader* postfx_frag = createShaderMSL(device_, POSTFX_FRAG_MSL, "postfx_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
||||
SDL_GPUShader* upscale_frag = createShaderMSL(device_, UPSCALE_FRAG_MSL, "upscale_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0);
|
||||
SDL_GPUShader* offscreen_frag = createShaderMSL(device_, POSTFX_FRAG_MSL, "postfx_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
||||
SDL_GPUShader* downscale_frag = createShaderMSL(device_, DOWNSCALE_FRAG_MSL, "downscale_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
||||
SDL_GPUShader* postfx_frag = createShaderMSL(device_, Msl::kPostfxFrag, "postfx_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
||||
#else
|
||||
SDL_GPUShader* postfx_frag = createShaderSPIRV(device_, kpostfx_frag_spv, kpostfx_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
||||
SDL_GPUShader* upscale_frag = createShaderSPIRV(device_, kupscale_frag_spv, kupscale_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0);
|
||||
SDL_GPUShader* offscreen_frag = createShaderSPIRV(device_, kpostfx_frag_spv, kpostfx_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
||||
SDL_GPUShader* downscale_frag = createShaderSPIRV(device_, kdownscale_frag_spv, kdownscale_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
||||
#endif
|
||||
|
||||
pipeline_ = createPostfxLikePipeline(postfx_frag, SWAPCHAIN_FMT, "PostFX");
|
||||
upscale_pipeline_ = createPostfxLikePipeline(upscale_frag, OFFSCREEN_FMT, "upscale");
|
||||
postfx_offscreen_pipeline_ = createPostfxLikePipeline(offscreen_frag, OFFSCREEN_FMT, "PostFX offscreen");
|
||||
downscale_pipeline_ = createPostfxLikePipeline(downscale_frag, SWAPCHAIN_FMT, "downscale");
|
||||
|
||||
return (pipeline_ != nullptr) && (upscale_pipeline_ != nullptr) && (postfx_offscreen_pipeline_ != nullptr) && (downscale_pipeline_ != nullptr);
|
||||
return pipeline_ != nullptr;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// createCrtPiPipeline — pipeline dedicado para el shader CRT-Pi.
|
||||
// Usa el mismo vertex shader que postfx (fullscreen-triangle genérico).
|
||||
// El fragment shader es específico para el algoritmo CRT-Pi.
|
||||
// Sin supersampling ni Lanczos: va siempre directo al swapchain.
|
||||
// ---------------------------------------------------------------------------
|
||||
auto SDL3GPUShader::createCrtPiPipeline() -> bool {
|
||||
const SDL_GPUTextureFormat SWAPCHAIN_FMT = SDL_GetGPUSwapchainTextureFormat(device_, window_);
|
||||
#ifdef __APPLE__
|
||||
SDL_GPUShader* frag = createShaderMSL(device_, CRTPI_FRAG_MSL, "crtpi_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
||||
SDL_GPUShader* frag = createShaderMSL(device_, Msl::kCrtpiFrag, "crtpi_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
||||
#else
|
||||
SDL_GPUShader* frag = createShaderSPIRV(device_, kcrtpi_frag_spv, kcrtpi_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
||||
#endif
|
||||
@@ -624,8 +250,6 @@ namespace Rendering {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// uploadPixels — copies ARGB8888 CPU pixels into the GPU transfer buffer.
|
||||
// Con supersampling (oversample_ > 1) expande cada pixel del juego a un bloque
|
||||
// oversample × oversample y hornea la scanline oscura en la última fila del bloque.
|
||||
// ---------------------------------------------------------------------------
|
||||
void SDL3GPUShader::uploadPixels(const Uint32* pixels, int width, int height) {
|
||||
if (!is_initialized_ || (upload_buffer_ == nullptr)) { return; }
|
||||
@@ -636,28 +260,11 @@ namespace Rendering {
|
||||
return;
|
||||
}
|
||||
|
||||
// Copia directa — el upscale lo hace la GPU en el primer render pass
|
||||
std::memcpy(mapped, pixels, static_cast<size_t>(width) * height * 4);
|
||||
|
||||
SDL_UnmapGPUTransferBuffer(device_, upload_buffer_);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// maybeRescaleSsTexture — recalcula factor SS i recrea scaled_texture_ si cal
|
||||
// ---------------------------------------------------------------------------
|
||||
void SDL3GPUShader::maybeRescaleSsTexture() {
|
||||
if (oversample_ <= 1 || game_height_ <= 0) { return; }
|
||||
int win_w = 0;
|
||||
int win_h = 0;
|
||||
SDL_GetWindowSizeInPixels(window_, &win_w, &win_h);
|
||||
const float ZOOM = static_cast<float>(win_h) / static_cast<float>(game_height_);
|
||||
const int NEED_FACTOR = calcSsFactor(ZOOM);
|
||||
if (NEED_FACTOR != ss_factor_) {
|
||||
SDL_WaitForGPUIdle(device_);
|
||||
recreateScaledTexture(NEED_FACTOR);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// uploadSceneTexture — copy pass: transfer buffer → scene texture
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -681,28 +288,6 @@ namespace Rendering {
|
||||
SDL_EndGPUCopyPass(copy);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// runUpscalePass — scene_texture_ → scaled_texture_ (NEAREST o LINEAR segons linear_upscale_)
|
||||
// ---------------------------------------------------------------------------
|
||||
void SDL3GPUShader::runUpscalePass(SDL_GPUCommandBuffer* cmd) {
|
||||
if (oversample_ <= 1 || scaled_texture_ == nullptr || upscale_pipeline_ == nullptr) { return; }
|
||||
|
||||
SDL_GPUColorTargetInfo target = {};
|
||||
target.texture = scaled_texture_;
|
||||
target.load_op = SDL_GPU_LOADOP_DONT_CARE;
|
||||
target.store_op = SDL_GPU_STOREOP_STORE;
|
||||
|
||||
SDL_GPURenderPass* pass = SDL_BeginGPURenderPass(cmd, &target, 1, nullptr);
|
||||
if (pass == nullptr) { return; }
|
||||
SDL_BindGPUGraphicsPipeline(pass, upscale_pipeline_);
|
||||
SDL_GPUTextureSamplerBinding binding = {};
|
||||
binding.texture = scene_texture_;
|
||||
binding.sampler = (linear_upscale_ && linear_sampler_ != nullptr) ? linear_sampler_ : sampler_;
|
||||
SDL_BindGPUFragmentSamplers(pass, 0, &binding, 1);
|
||||
SDL_DrawGPUPrimitives(pass, 3, 1, 0, 0);
|
||||
SDL_EndGPURenderPass(pass);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// computeViewport — dimensions lògiques del canvas dins del swapchain (letterbox)
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -726,17 +311,11 @@ namespace Rendering {
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// updateDynamicUniforms — actualitza pixel_scale, time, oversample per a aquest frame
|
||||
// updateDynamicUniforms — actualitza pixel_scale i time per a aquest frame
|
||||
// ---------------------------------------------------------------------------
|
||||
void SDL3GPUShader::updateDynamicUniforms(float viewport_h) {
|
||||
// pixel_scale: subpíxels per pixel lògic. Amb SS: ss_factor_ exacte; sense SS: zoom de finestra.
|
||||
if (oversample_ > 1 && ss_factor_ > 0) {
|
||||
uniforms_.pixel_scale = static_cast<float>(ss_factor_);
|
||||
} else {
|
||||
uniforms_.pixel_scale = (game_height_ > 0) ? (viewport_h / static_cast<float>(game_height_)) : 1.0F;
|
||||
}
|
||||
uniforms_.pixel_scale = (game_height_ > 0) ? (viewport_h / static_cast<float>(game_height_)) : 1.0F;
|
||||
uniforms_.time = static_cast<float>(SDL_GetTicks()) / 1000.0F;
|
||||
uniforms_.oversample = (oversample_ > 1 && ss_factor_ > 0) ? static_cast<float>(ss_factor_) : 1.0F;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -769,53 +348,7 @@ namespace Rendering {
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// runLanczosPasses — scaled_texture_ → postfx_texture_ (PostFX) → swapchain (Lanczos)
|
||||
// ---------------------------------------------------------------------------
|
||||
void SDL3GPUShader::runLanczosPasses(SDL_GPUCommandBuffer* cmd, SDL_GPUTexture* swapchain, const Viewport& vp) {
|
||||
// Pass A: PostFX → postfx_texture_ (full scaled size, sense viewport)
|
||||
SDL_GPUColorTargetInfo postfx_target = {};
|
||||
postfx_target.texture = postfx_texture_;
|
||||
postfx_target.load_op = SDL_GPU_LOADOP_CLEAR;
|
||||
postfx_target.store_op = SDL_GPU_STOREOP_STORE;
|
||||
postfx_target.clear_color = {.r = 0.0F, .g = 0.0F, .b = 0.0F, .a = 1.0F};
|
||||
|
||||
SDL_GPURenderPass* ppass = SDL_BeginGPURenderPass(cmd, &postfx_target, 1, nullptr);
|
||||
if (ppass != nullptr) {
|
||||
SDL_BindGPUGraphicsPipeline(ppass, postfx_offscreen_pipeline_);
|
||||
SDL_GPUTextureSamplerBinding pbinding = {};
|
||||
pbinding.texture = scaled_texture_;
|
||||
pbinding.sampler = sampler_; // NEAREST: 1:1 pass, efectes calculats analíticament
|
||||
SDL_BindGPUFragmentSamplers(ppass, 0, &pbinding, 1);
|
||||
SDL_PushGPUFragmentUniformData(cmd, 0, &uniforms_, sizeof(PostFXUniforms));
|
||||
SDL_DrawGPUPrimitives(ppass, 3, 1, 0, 0);
|
||||
SDL_EndGPURenderPass(ppass);
|
||||
}
|
||||
|
||||
// Pass B: Downscale Lanczos → swapchain (amb viewport/letterbox)
|
||||
SDL_GPUColorTargetInfo ds_target = {};
|
||||
ds_target.texture = swapchain;
|
||||
ds_target.load_op = SDL_GPU_LOADOP_CLEAR;
|
||||
ds_target.store_op = SDL_GPU_STOREOP_STORE;
|
||||
ds_target.clear_color = {.r = 0.0F, .g = 0.0F, .b = 0.0F, .a = 1.0F};
|
||||
|
||||
SDL_GPURenderPass* dpass = SDL_BeginGPURenderPass(cmd, &ds_target, 1, nullptr);
|
||||
if (dpass == nullptr) { return; }
|
||||
SDL_BindGPUGraphicsPipeline(dpass, downscale_pipeline_);
|
||||
SDL_GPUViewport sdlvp = {.x = vp.x, .y = vp.y, .w = vp.w, .h = vp.h, .min_depth = 0.0F, .max_depth = 1.0F};
|
||||
SDL_SetGPUViewport(dpass, &sdlvp);
|
||||
SDL_GPUTextureSamplerBinding dbinding = {};
|
||||
dbinding.texture = postfx_texture_;
|
||||
dbinding.sampler = sampler_; // NEAREST: el shader Lanczos fa la seua pròpia interpolació
|
||||
SDL_BindGPUFragmentSamplers(dpass, 0, &dbinding, 1);
|
||||
// algorithm: 0=Lanczos2, 1=Lanczos3 (downscale_algo_ és 1-based)
|
||||
DownscaleUniforms downscale_u = {.algorithm = downscale_algo_ - 1, .pad0 = 0.0F, .pad1 = 0.0F, .pad2 = 0.0F};
|
||||
SDL_PushGPUFragmentUniformData(cmd, 0, &downscale_u, sizeof(DownscaleUniforms));
|
||||
SDL_DrawGPUPrimitives(dpass, 3, 1, 0, 0);
|
||||
SDL_EndGPURenderPass(dpass);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// runDirectPostfxPass — PostFX → swapchain directament (sense Lanczos)
|
||||
// runDirectPostfxPass — PostFX → swapchain directament
|
||||
// ---------------------------------------------------------------------------
|
||||
void SDL3GPUShader::runDirectPostfxPass(SDL_GPUCommandBuffer* cmd, SDL_GPUTexture* swapchain, const Viewport& vp) {
|
||||
SDL_GPUColorTargetInfo color_target = {};
|
||||
@@ -830,13 +363,9 @@ namespace Rendering {
|
||||
SDL_GPUViewport sdlvp = {.x = vp.x, .y = vp.y, .w = vp.w, .h = vp.h, .min_depth = 0.0F, .max_depth = 1.0F};
|
||||
SDL_SetGPUViewport(pass, &sdlvp);
|
||||
|
||||
// Amb SS: llegir de scaled_texture_ amb LINEAR; sense SS: scene_texture_ amb NEAREST.
|
||||
SDL_GPUTexture* input_texture = (oversample_ > 1 && scaled_texture_ != nullptr) ? scaled_texture_ : scene_texture_;
|
||||
SDL_GPUSampler* active_sampler = (oversample_ > 1 && linear_sampler_ != nullptr) ? linear_sampler_ : sampler_;
|
||||
|
||||
SDL_GPUTextureSamplerBinding binding = {};
|
||||
binding.texture = input_texture;
|
||||
binding.sampler = active_sampler;
|
||||
binding.texture = scene_texture_;
|
||||
binding.sampler = sampler_;
|
||||
SDL_BindGPUFragmentSamplers(pass, 0, &binding, 1);
|
||||
|
||||
SDL_PushGPUFragmentUniformData(cmd, 0, &uniforms_, sizeof(PostFXUniforms));
|
||||
@@ -845,13 +374,11 @@ namespace Rendering {
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// render — orquestra upload + upscale + path PostFX (CrtPi / Lanczos / direct)
|
||||
// render — orquestra upload + path PostFX (CrtPi / direct)
|
||||
// ---------------------------------------------------------------------------
|
||||
void SDL3GPUShader::render() {
|
||||
if (!is_initialized_) { return; }
|
||||
|
||||
maybeRescaleSsTexture();
|
||||
|
||||
SDL_GPUCommandBuffer* cmd = SDL_AcquireGPUCommandBuffer(device_);
|
||||
if (cmd == nullptr) {
|
||||
SDL_Log("SDL3GPUShader: SDL_AcquireGPUCommandBuffer failed: %s", SDL_GetError());
|
||||
@@ -859,7 +386,6 @@ namespace Rendering {
|
||||
}
|
||||
|
||||
uploadSceneTexture(cmd);
|
||||
runUpscalePass(cmd);
|
||||
|
||||
SDL_GPUTexture* swapchain = nullptr;
|
||||
Uint32 sw = 0;
|
||||
@@ -878,12 +404,8 @@ namespace Rendering {
|
||||
const Viewport VP = computeViewport(sw, sh);
|
||||
updateDynamicUniforms(VP.h);
|
||||
|
||||
const bool USE_LANCZOS = (oversample_ > 1 && downscale_algo_ > 0 && scaled_texture_ != nullptr && postfx_texture_ != nullptr && postfx_offscreen_pipeline_ != nullptr && downscale_pipeline_ != nullptr);
|
||||
|
||||
if (active_shader_ == ShaderType::CRTPI && crtpi_pipeline_ != nullptr) {
|
||||
runCrtPiPass(cmd, swapchain, VP);
|
||||
} else if (USE_LANCZOS) {
|
||||
runLanczosPasses(cmd, swapchain, VP);
|
||||
} else {
|
||||
runDirectPostfxPass(cmd, swapchain, VP);
|
||||
}
|
||||
@@ -908,31 +430,10 @@ namespace Rendering {
|
||||
SDL_ReleaseGPUGraphicsPipeline(device_, crtpi_pipeline_);
|
||||
crtpi_pipeline_ = nullptr;
|
||||
}
|
||||
if (postfx_offscreen_pipeline_ != nullptr) {
|
||||
SDL_ReleaseGPUGraphicsPipeline(device_, postfx_offscreen_pipeline_);
|
||||
postfx_offscreen_pipeline_ = nullptr;
|
||||
}
|
||||
if (upscale_pipeline_ != nullptr) {
|
||||
SDL_ReleaseGPUGraphicsPipeline(device_, upscale_pipeline_);
|
||||
upscale_pipeline_ = nullptr;
|
||||
}
|
||||
if (downscale_pipeline_ != nullptr) {
|
||||
SDL_ReleaseGPUGraphicsPipeline(device_, downscale_pipeline_);
|
||||
downscale_pipeline_ = nullptr;
|
||||
}
|
||||
if (scene_texture_ != nullptr) {
|
||||
SDL_ReleaseGPUTexture(device_, scene_texture_);
|
||||
scene_texture_ = nullptr;
|
||||
}
|
||||
if (scaled_texture_ != nullptr) {
|
||||
SDL_ReleaseGPUTexture(device_, scaled_texture_);
|
||||
scaled_texture_ = nullptr;
|
||||
}
|
||||
if (postfx_texture_ != nullptr) {
|
||||
SDL_ReleaseGPUTexture(device_, postfx_texture_);
|
||||
postfx_texture_ = nullptr;
|
||||
}
|
||||
ss_factor_ = 0;
|
||||
if (upload_buffer_ != nullptr) {
|
||||
SDL_ReleaseGPUTransferBuffer(device_, upload_buffer_);
|
||||
upload_buffer_ = nullptr;
|
||||
@@ -941,10 +442,6 @@ namespace Rendering {
|
||||
SDL_ReleaseGPUSampler(device_, sampler_);
|
||||
sampler_ = nullptr;
|
||||
}
|
||||
if (linear_sampler_ != nullptr) {
|
||||
SDL_ReleaseGPUSampler(device_, linear_sampler_);
|
||||
linear_sampler_ = nullptr;
|
||||
}
|
||||
// device_ y el claim de la ventana se mantienen vivos
|
||||
}
|
||||
}
|
||||
@@ -1013,15 +510,17 @@ namespace Rendering {
|
||||
|
||||
void SDL3GPUShader::setPostFXParams(const PostFXParams& p) {
|
||||
uniforms_.vignette_strength = p.vignette;
|
||||
uniforms_.chroma_strength = p.chroma;
|
||||
uniforms_.chroma_min = p.chroma_min;
|
||||
uniforms_.chroma_max = p.chroma_max;
|
||||
uniforms_.mask_strength = p.mask;
|
||||
uniforms_.gamma_strength = p.gamma;
|
||||
uniforms_.curvature = p.curvature;
|
||||
uniforms_.bleeding = p.bleeding;
|
||||
uniforms_.flicker = p.flicker;
|
||||
|
||||
// Las scanlines siempre las aplica el shader PostFX en GPU.
|
||||
uniforms_.scanline_strength = p.scanlines;
|
||||
uniforms_.scan_dark_ratio = p.scan_dark_ratio;
|
||||
uniforms_.scan_dark_floor = p.scan_dark_floor;
|
||||
uniforms_.scan_edge_soft = p.scan_edge_soft;
|
||||
}
|
||||
|
||||
void SDL3GPUShader::setCrtPiParams(const CrtPiParams& p) {
|
||||
@@ -1075,34 +574,8 @@ namespace Rendering {
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// setOversample — cambia el factor SS; recrea texturas si ya está inicializado
|
||||
// ---------------------------------------------------------------------------
|
||||
void SDL3GPUShader::setOversample(int factor) {
|
||||
const int NEW_FACTOR = std::max(1, factor);
|
||||
if (NEW_FACTOR == oversample_) { return; }
|
||||
oversample_ = NEW_FACTOR;
|
||||
if (is_initialized_) {
|
||||
reinitTexturesAndBuffer();
|
||||
// scanline_strength se actualizará en el próximo setPostFXParams
|
||||
}
|
||||
}
|
||||
|
||||
void SDL3GPUShader::setLinearUpscale(bool linear) {
|
||||
linear_upscale_ = linear;
|
||||
}
|
||||
|
||||
void SDL3GPUShader::setDownscaleAlgo(int algo) {
|
||||
downscale_algo_ = std::max(0, std::min(algo, 2));
|
||||
}
|
||||
|
||||
auto SDL3GPUShader::getSsTextureSize() const -> std::pair<int, int> {
|
||||
if (ss_factor_ <= 1) { return {0, 0}; }
|
||||
return {game_width_ * ss_factor_, game_height_ * ss_factor_};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// reinitTexturesAndBuffer — recrea scene_texture_, scaled_texture_ y
|
||||
// upload_buffer_ con el factor oversample_ actual. No toca pipelines ni samplers.
|
||||
// reinitTexturesAndBuffer — recrea scene_texture_ i upload_buffer_.
|
||||
// No toca pipelines ni samplers.
|
||||
// ---------------------------------------------------------------------------
|
||||
auto SDL3GPUShader::reinitTexturesAndBuffer() -> bool {
|
||||
if (device_ == nullptr) { return false; }
|
||||
@@ -1112,12 +585,6 @@ namespace Rendering {
|
||||
SDL_ReleaseGPUTexture(device_, scene_texture_);
|
||||
scene_texture_ = nullptr;
|
||||
}
|
||||
// scaled_texture_ se libera aquí; se recreará en el primer render() con el factor correcto
|
||||
if (scaled_texture_ != nullptr) {
|
||||
SDL_ReleaseGPUTexture(device_, scaled_texture_);
|
||||
scaled_texture_ = nullptr;
|
||||
}
|
||||
ss_factor_ = 0;
|
||||
|
||||
if (upload_buffer_ != nullptr) {
|
||||
SDL_ReleaseGPUTransferBuffer(device_, upload_buffer_);
|
||||
@@ -1125,9 +592,7 @@ namespace Rendering {
|
||||
}
|
||||
|
||||
uniforms_.screen_height = static_cast<float>(game_height_);
|
||||
uniforms_.oversample = static_cast<float>(oversample_);
|
||||
|
||||
// scene_texture_: siempre a resolución del juego
|
||||
SDL_GPUTextureCreateInfo tex_info = {};
|
||||
tex_info.type = SDL_GPU_TEXTURETYPE_2D;
|
||||
tex_info.format = SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM;
|
||||
@@ -1142,7 +607,6 @@ namespace Rendering {
|
||||
return false;
|
||||
}
|
||||
|
||||
// upload_buffer_: siempre a resolución del juego
|
||||
SDL_GPUTransferBufferCreateInfo tb_info = {};
|
||||
tb_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
|
||||
tb_info.size = static_cast<Uint32>(game_width_ * game_height_ * 4);
|
||||
@@ -1154,74 +618,6 @@ namespace Rendering {
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_Log("SDL3GPUShader: reinit — scene %dx%d, SS %s (scaled se creará en render)",
|
||||
game_width_,
|
||||
game_height_,
|
||||
oversample_ > 1 ? "on" : "off");
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// calcSsFactor — primer múltiplo de 3 >= zoom, mínimo 3.
|
||||
// Ejemplos: zoom 1,2,3 → 3; zoom 4,5,6 → 6; zoom 4.4 → 6; zoom 7,8,9 → 9.
|
||||
// ---------------------------------------------------------------------------
|
||||
auto SDL3GPUShader::calcSsFactor(float zoom) -> int {
|
||||
const int MULTIPLE = 3;
|
||||
const int N = static_cast<int>(std::ceil(zoom / static_cast<float>(MULTIPLE)));
|
||||
return std::max(1, N) * MULTIPLE;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// recreateScaledTexture — libera y recrea scaled_texture_ para el factor dado.
|
||||
// Llamar solo cuando device_ no esté ejecutando comandos (SDL_WaitForGPUIdle previo).
|
||||
// ---------------------------------------------------------------------------
|
||||
auto SDL3GPUShader::recreateScaledTexture(int factor) -> bool {
|
||||
if (scaled_texture_ != nullptr) {
|
||||
SDL_ReleaseGPUTexture(device_, scaled_texture_);
|
||||
scaled_texture_ = nullptr;
|
||||
}
|
||||
if (postfx_texture_ != nullptr) {
|
||||
SDL_ReleaseGPUTexture(device_, postfx_texture_);
|
||||
postfx_texture_ = nullptr;
|
||||
}
|
||||
ss_factor_ = 0;
|
||||
|
||||
const int W = game_width_ * factor;
|
||||
const int H = game_height_ * factor;
|
||||
|
||||
SDL_GPUTextureCreateInfo info = {};
|
||||
info.type = SDL_GPU_TEXTURETYPE_2D;
|
||||
info.format = SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM;
|
||||
info.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER | SDL_GPU_TEXTUREUSAGE_COLOR_TARGET;
|
||||
info.width = static_cast<Uint32>(W);
|
||||
info.height = static_cast<Uint32>(H);
|
||||
info.layer_count_or_depth = 1;
|
||||
info.num_levels = 1;
|
||||
|
||||
scaled_texture_ = SDL_CreateGPUTexture(device_, &info);
|
||||
if (scaled_texture_ == nullptr) {
|
||||
SDL_Log("SDL3GPUShader: failed to create scaled texture %dx%d (factor %d): %s",
|
||||
W,
|
||||
H,
|
||||
factor,
|
||||
SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
postfx_texture_ = SDL_CreateGPUTexture(device_, &info);
|
||||
if (postfx_texture_ == nullptr) {
|
||||
SDL_Log("SDL3GPUShader: failed to create postfx texture %dx%d (factor %d): %s",
|
||||
W,
|
||||
H,
|
||||
factor,
|
||||
SDL_GetError());
|
||||
SDL_ReleaseGPUTexture(device_, scaled_texture_);
|
||||
scaled_texture_ = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
ss_factor_ = factor;
|
||||
SDL_Log("SDL3GPUShader: scaled+postfx textures %dx%d (factor %d×)", W, H, factor);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user