forked from jaildesigner-jailgames/jaildoctors_dilemma
imlementant supersampling
This commit is contained in:
@@ -24,8 +24,8 @@ layout(set = 3, binding = 0) uniform PostFXUniforms {
|
|||||||
float bleeding;
|
float bleeding;
|
||||||
float pixel_scale; // physical pixels per logical pixel (vh / tex_height_)
|
float pixel_scale; // physical pixels per logical pixel (vh / tex_height_)
|
||||||
float time; // seconds since SDL init (for future animated effects)
|
float time; // seconds since SDL init (for future animated effects)
|
||||||
float pad0; // padding — 48 bytes total (3 × 16)
|
float oversample; // supersampling factor (1.0 = off, 3.0 = 3×SS)
|
||||||
float pad1;
|
float pad1; // padding — 48 bytes total (3 × 16)
|
||||||
} u;
|
} u;
|
||||||
|
|
||||||
// YCbCr helpers for NTSC bleeding
|
// YCbCr helpers for NTSC bleeding
|
||||||
@@ -68,15 +68,17 @@ void main() {
|
|||||||
// Muestra base
|
// Muestra base
|
||||||
vec3 base = texture(scene, uv).rgb;
|
vec3 base = texture(scene, uv).rgb;
|
||||||
|
|
||||||
// Sangrado NTSC — difuminado horizontal de crominancia
|
// Sangrado NTSC — difuminado horizontal de crominancia.
|
||||||
|
// step = 1 pixel lógico de juego en UV (corrige SS: textureSize.x = game_w * oversample).
|
||||||
vec3 colour;
|
vec3 colour;
|
||||||
if (u.bleeding > 0.0) {
|
if (u.bleeding > 0.0) {
|
||||||
float tw = float(textureSize(scene, 0).x);
|
float tw = float(textureSize(scene, 0).x);
|
||||||
|
float step = u.oversample / tw; // 1 pixel lógico en UV
|
||||||
vec3 ycc = rgb_to_ycc(base);
|
vec3 ycc = rgb_to_ycc(base);
|
||||||
vec3 ycc_l2 = rgb_to_ycc(texture(scene, uv - vec2(2.0/tw, 0.0)).rgb);
|
vec3 ycc_l2 = rgb_to_ycc(texture(scene, uv - vec2(2.0*step, 0.0)).rgb);
|
||||||
vec3 ycc_l1 = rgb_to_ycc(texture(scene, uv - vec2(1.0/tw, 0.0)).rgb);
|
vec3 ycc_l1 = rgb_to_ycc(texture(scene, uv - vec2(1.0*step, 0.0)).rgb);
|
||||||
vec3 ycc_r1 = rgb_to_ycc(texture(scene, uv + vec2(1.0/tw, 0.0)).rgb);
|
vec3 ycc_r1 = rgb_to_ycc(texture(scene, uv + vec2(1.0*step, 0.0)).rgb);
|
||||||
vec3 ycc_r2 = rgb_to_ycc(texture(scene, uv + vec2(2.0/tw, 0.0)).rgb);
|
vec3 ycc_r2 = rgb_to_ycc(texture(scene, uv + vec2(2.0*step, 0.0)).rgb);
|
||||||
ycc.yz = (ycc_l2.yz + ycc_l1.yz*2.0 + ycc.yz*2.0 + ycc_r1.yz*2.0 + ycc_r2.yz) / 8.0;
|
ycc.yz = (ycc_l2.yz + ycc_l1.yz*2.0 + ycc.yz*2.0 + ycc_r1.yz*2.0 + ycc_r2.yz) / 8.0;
|
||||||
colour = mix(base, ycc_to_rgb(ycc), u.bleeding);
|
colour = mix(base, ycc_to_rgb(ycc), u.bleeding);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -463,7 +463,10 @@ auto loadData(const std::string& filepath) -> std::vector<uint8_t> {
|
|||||||
void Screen::applyCurrentPostFXPreset() {
|
void Screen::applyCurrentPostFXPreset() {
|
||||||
if (shader_backend_ && !Options::postfx_presets.empty()) {
|
if (shader_backend_ && !Options::postfx_presets.empty()) {
|
||||||
const auto& p = Options::postfx_presets[static_cast<size_t>(Options::current_postfx_preset)];
|
const auto& p = Options::postfx_presets[static_cast<size_t>(Options::current_postfx_preset)];
|
||||||
Rendering::PostFXParams params{.vignette = p.vignette, .scanlines = p.scanlines, .chroma = p.chroma, .mask = p.mask, .gamma = p.gamma, .curvature = p.curvature, .bleeding = p.bleeding};
|
// setOversample primero: puede recrear texturas y debe conocer el factor
|
||||||
|
// antes de que setPostFXParams decida si hornear scanlines en CPU o GPU.
|
||||||
|
shader_backend_->setOversample(p.supersampling ? 3 : 1);
|
||||||
|
Rendering::PostFXParams params{.vignette = p.vignette, .scanlines = p.scanlines, .chroma = p.chroma, .mask = p.mask, .gamma = p.gamma, .curvature = p.curvature, .bleeding = p.bleeding, .supersampling = p.supersampling};
|
||||||
shader_backend_->setPostFXParams(params);
|
shader_backend_->setPostFXParams(params);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ struct PostFXUniforms {
|
|||||||
float bleeding;
|
float bleeding;
|
||||||
float pixel_scale;
|
float pixel_scale;
|
||||||
float time;
|
float time;
|
||||||
float pad0;
|
float oversample; // 1.0 = sin SS, 3.0 = 3× supersampling
|
||||||
float pad1;
|
float pad1;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -102,15 +102,17 @@ fragment float4 postfx_fs(PostVOut in [[stage_in]],
|
|||||||
// Muestra base
|
// Muestra base
|
||||||
float3 base = scene.sample(samp, uv).rgb;
|
float3 base = scene.sample(samp, uv).rgb;
|
||||||
|
|
||||||
// Sangrado NTSC — difuminado horizontal de crominancia
|
// Sangrado NTSC — difuminado horizontal de crominancia.
|
||||||
|
// step = 1 pixel de juego en espacio UV (corrige SS: scene.get_width() = game_w * oversample).
|
||||||
float3 colour;
|
float3 colour;
|
||||||
if (u.bleeding > 0.0f) {
|
if (u.bleeding > 0.0f) {
|
||||||
float tw = float(scene.get_width());
|
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 = rgb_to_ycc(base);
|
||||||
float3 ycc_l2 = rgb_to_ycc(scene.sample(samp, uv - float2(2.0f/tw, 0.0f)).rgb);
|
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/tw, 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/tw, 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/tw, 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;
|
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);
|
colour = mix(base, ycc_to_rgb(ycc), u.bleeding);
|
||||||
} else {
|
} else {
|
||||||
@@ -199,9 +201,12 @@ namespace Rendering {
|
|||||||
float fw = 0.0F;
|
float fw = 0.0F;
|
||||||
float fh = 0.0F;
|
float fh = 0.0F;
|
||||||
SDL_GetTextureSize(texture, &fw, &fh);
|
SDL_GetTextureSize(texture, &fw, &fh);
|
||||||
tex_width_ = static_cast<int>(fw);
|
game_width_ = static_cast<int>(fw);
|
||||||
tex_height_ = static_cast<int>(fh);
|
game_height_ = static_cast<int>(fh);
|
||||||
uniforms_.screen_height = fh; // Altura lógica del juego (no el swapchain físico)
|
tex_width_ = game_width_ * oversample_;
|
||||||
|
tex_height_ = game_height_ * oversample_;
|
||||||
|
uniforms_.screen_height = static_cast<float>(tex_height_); // Altura de la textura GPU
|
||||||
|
uniforms_.oversample = static_cast<float>(oversample_);
|
||||||
|
|
||||||
// ----------------------------------------------------------------
|
// ----------------------------------------------------------------
|
||||||
// 1. Create GPU device (solo si no existe ya)
|
// 1. Create GPU device (solo si no existe ya)
|
||||||
@@ -264,7 +269,7 @@ namespace Rendering {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------
|
// ----------------------------------------------------------------
|
||||||
// 5. Create nearest-neighbour sampler (retro pixel art)
|
// 5. Create samplers: NEAREST (pixel art) + LINEAR (supersampling)
|
||||||
// ----------------------------------------------------------------
|
// ----------------------------------------------------------------
|
||||||
SDL_GPUSamplerCreateInfo samp_info = {};
|
SDL_GPUSamplerCreateInfo samp_info = {};
|
||||||
samp_info.min_filter = SDL_GPU_FILTER_NEAREST;
|
samp_info.min_filter = SDL_GPU_FILTER_NEAREST;
|
||||||
@@ -280,6 +285,20 @@ namespace Rendering {
|
|||||||
return false;
|
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
|
// 6. Create PostFX graphics pipeline
|
||||||
// ----------------------------------------------------------------
|
// ----------------------------------------------------------------
|
||||||
@@ -345,7 +364,9 @@ namespace Rendering {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// uploadPixels — copies ARGB8888 CPU pixels into the GPU transfer buffer
|
// 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) {
|
void SDL3GPUShader::uploadPixels(const Uint32* pixels, int width, int height) {
|
||||||
if (!is_initialized_ || (upload_buffer_ == nullptr)) { return; }
|
if (!is_initialized_ || (upload_buffer_ == nullptr)) { return; }
|
||||||
@@ -355,7 +376,45 @@ namespace Rendering {
|
|||||||
SDL_Log("SDL3GPUShader: SDL_MapGPUTransferBuffer failed: %s", SDL_GetError());
|
SDL_Log("SDL3GPUShader: SDL_MapGPUTransferBuffer failed: %s", SDL_GetError());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (oversample_ <= 1) {
|
||||||
|
// Path sin supersampling: copia directa
|
||||||
std::memcpy(mapped, pixels, static_cast<size_t>(width * height * 4));
|
std::memcpy(mapped, pixels, static_cast<size_t>(width * height * 4));
|
||||||
|
} else {
|
||||||
|
// Path con supersampling: expande cada pixel a OS×OS, oscurece última fila.
|
||||||
|
// Replica la fórmula del shader: mix(3.5, 0.42, scanline_strength).
|
||||||
|
auto* out = static_cast<Uint32*>(mapped);
|
||||||
|
const int OS = oversample_;
|
||||||
|
const float BRIGHT_MUL = 1.0F + (baked_scanline_strength_ * 2.5F); // rows 0..OS-2
|
||||||
|
const float DARK_MUL = 1.0F - (baked_scanline_strength_ * 0.58F); // row OS-1
|
||||||
|
|
||||||
|
for (int y = 0; y < height; ++y) {
|
||||||
|
for (int x = 0; x < width; ++x) {
|
||||||
|
const Uint32 SRC = pixels[y * width + x];
|
||||||
|
const Uint32 ALPHA = (SRC >> 24) & 0xFFU;
|
||||||
|
const auto FR = static_cast<float>((SRC >> 16) & 0xFFU);
|
||||||
|
const auto FG = static_cast<float>((SRC >> 8) & 0xFFU);
|
||||||
|
const auto FB = static_cast<float>( SRC & 0xFFU);
|
||||||
|
|
||||||
|
auto make_px = [ALPHA](float rv, float gv, float bv) -> Uint32 {
|
||||||
|
auto cl = [](float v) -> Uint32 { return static_cast<Uint32>(std::min(255.0F, v)); };
|
||||||
|
return (ALPHA << 24) | (cl(rv) << 16) | (cl(gv) << 8) | cl(bv);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Uint32 BRIGHT = make_px(FR * BRIGHT_MUL, FG * BRIGHT_MUL, FB * BRIGHT_MUL);
|
||||||
|
const Uint32 DARK = make_px(FR * DARK_MUL, FG * DARK_MUL, FB * DARK_MUL);
|
||||||
|
|
||||||
|
for (int dy = 0; dy < OS; ++dy) {
|
||||||
|
const Uint32 OUT_PX = (dy == OS - 1) ? DARK : BRIGHT;
|
||||||
|
const int DST_Y = (y * OS) + dy;
|
||||||
|
for (int dx = 0; dx < OS; ++dx) {
|
||||||
|
out[DST_Y * (width * OS) + (x * OS) + dx] = OUT_PX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SDL_UnmapGPUTransferBuffer(device_, upload_buffer_);
|
SDL_UnmapGPUTransferBuffer(device_, upload_buffer_);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -416,36 +475,45 @@ namespace Rendering {
|
|||||||
if (pass != nullptr) {
|
if (pass != nullptr) {
|
||||||
SDL_BindGPUGraphicsPipeline(pass, pipeline_);
|
SDL_BindGPUGraphicsPipeline(pass, pipeline_);
|
||||||
|
|
||||||
// Calcular viewport para mantener relación de aspecto (letterbox o integer scale)
|
// Calcular viewport usando las dimensiones lógicas del canvas (game_width_/height_),
|
||||||
|
// no las de la textura GPU (que pueden ser game×3 con supersampling).
|
||||||
|
// El GPU escala la textura para cubrir el viewport independientemente de su resolución.
|
||||||
float vx = 0.0F;
|
float vx = 0.0F;
|
||||||
float vy = 0.0F;
|
float vy = 0.0F;
|
||||||
float vw = 0.0F;
|
float vw = 0.0F;
|
||||||
float vh = 0.0F;
|
float vh = 0.0F;
|
||||||
if (integer_scale_) {
|
if (integer_scale_) {
|
||||||
const int SCALE = std::max(1, std::min(static_cast<int>(sw) / tex_width_, static_cast<int>(sh) / tex_height_));
|
const int SCALE = std::max(1, std::min(static_cast<int>(sw) / game_width_, static_cast<int>(sh) / game_height_));
|
||||||
vw = static_cast<float>(tex_width_ * SCALE);
|
vw = static_cast<float>(game_width_ * SCALE);
|
||||||
vh = static_cast<float>(tex_height_ * SCALE);
|
vh = static_cast<float>(game_height_ * SCALE);
|
||||||
} else {
|
} else {
|
||||||
const float SCALE = std::min(
|
const float SCALE = std::min(
|
||||||
static_cast<float>(sw) / static_cast<float>(tex_width_),
|
static_cast<float>(sw) / static_cast<float>(game_width_),
|
||||||
static_cast<float>(sh) / static_cast<float>(tex_height_));
|
static_cast<float>(sh) / static_cast<float>(game_height_));
|
||||||
vw = static_cast<float>(tex_width_) * SCALE;
|
vw = static_cast<float>(game_width_) * SCALE;
|
||||||
vh = static_cast<float>(tex_height_) * SCALE;
|
vh = static_cast<float>(game_height_) * SCALE;
|
||||||
}
|
}
|
||||||
vx = std::floor((static_cast<float>(sw) - vw) * 0.5F);
|
vx = std::floor((static_cast<float>(sw) - vw) * 0.5F);
|
||||||
vy = std::floor((static_cast<float>(sh) - vh) * 0.5F);
|
vy = std::floor((static_cast<float>(sh) - vh) * 0.5F);
|
||||||
SDL_GPUViewport vp = {vx, vy, vw, vh, 0.0F, 1.0F};
|
SDL_GPUViewport vp = {vx, vy, vw, vh, 0.0F, 1.0F};
|
||||||
SDL_SetGPUViewport(pass, &vp);
|
SDL_SetGPUViewport(pass, &vp);
|
||||||
|
|
||||||
// Uniforms dinámicos: pixel_scale y time
|
// pixel_scale: pixels físicos por pixel lógico de juego (para scanlines sin SS).
|
||||||
uniforms_.pixel_scale = (tex_height_ > 0)
|
// Con SS las scanlines están horneadas en CPU → scanline_strength=0 → no se usa.
|
||||||
? (vh / static_cast<float>(tex_height_))
|
uniforms_.pixel_scale = (game_height_ > 0)
|
||||||
|
? (vh / static_cast<float>(game_height_))
|
||||||
: 1.0F;
|
: 1.0F;
|
||||||
uniforms_.time = static_cast<float>(SDL_GetTicks()) / 1000.0F;
|
uniforms_.time = static_cast<float>(SDL_GetTicks()) / 1000.0F;
|
||||||
|
uniforms_.oversample = static_cast<float>(oversample_);
|
||||||
|
|
||||||
|
// Con supersampling usamos LINEAR para que el escalado a zooms no-múltiplo-de-3
|
||||||
|
// promedia correctamente las filas de scanline horneadas en CPU.
|
||||||
|
SDL_GPUSampler* active_sampler = (oversample_ > 1 && linear_sampler_ != nullptr)
|
||||||
|
? linear_sampler_ : sampler_;
|
||||||
|
|
||||||
SDL_GPUTextureSamplerBinding binding = {};
|
SDL_GPUTextureSamplerBinding binding = {};
|
||||||
binding.texture = scene_texture_;
|
binding.texture = scene_texture_;
|
||||||
binding.sampler = sampler_;
|
binding.sampler = active_sampler;
|
||||||
SDL_BindGPUFragmentSamplers(pass, 0, &binding, 1);
|
SDL_BindGPUFragmentSamplers(pass, 0, &binding, 1);
|
||||||
|
|
||||||
SDL_PushGPUFragmentUniformData(cmd, 0, &uniforms_, sizeof(PostFXUniforms));
|
SDL_PushGPUFragmentUniformData(cmd, 0, &uniforms_, sizeof(PostFXUniforms));
|
||||||
@@ -482,6 +550,10 @@ namespace Rendering {
|
|||||||
SDL_ReleaseGPUSampler(device_, sampler_);
|
SDL_ReleaseGPUSampler(device_, sampler_);
|
||||||
sampler_ = nullptr;
|
sampler_ = nullptr;
|
||||||
}
|
}
|
||||||
|
if (linear_sampler_ != nullptr) {
|
||||||
|
SDL_ReleaseGPUSampler(device_, linear_sampler_);
|
||||||
|
linear_sampler_ = nullptr;
|
||||||
|
}
|
||||||
// device_ y el claim de la ventana se mantienen vivos
|
// device_ y el claim de la ventana se mantienen vivos
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -550,12 +622,16 @@ namespace Rendering {
|
|||||||
|
|
||||||
void SDL3GPUShader::setPostFXParams(const PostFXParams& p) {
|
void SDL3GPUShader::setPostFXParams(const PostFXParams& p) {
|
||||||
uniforms_.vignette_strength = p.vignette;
|
uniforms_.vignette_strength = p.vignette;
|
||||||
uniforms_.scanline_strength = p.scanlines;
|
|
||||||
uniforms_.chroma_strength = p.chroma;
|
uniforms_.chroma_strength = p.chroma;
|
||||||
uniforms_.mask_strength = p.mask;
|
uniforms_.mask_strength = p.mask;
|
||||||
uniforms_.gamma_strength = p.gamma;
|
uniforms_.gamma_strength = p.gamma;
|
||||||
uniforms_.curvature = p.curvature;
|
uniforms_.curvature = p.curvature;
|
||||||
uniforms_.bleeding = p.bleeding;
|
uniforms_.bleeding = p.bleeding;
|
||||||
|
|
||||||
|
// Con supersampling las scanlines se hornean en CPU (uploadPixels).
|
||||||
|
// El shader recibe strength=0 para no aplicarlas de nuevo en GPU.
|
||||||
|
baked_scanline_strength_ = p.scanlines;
|
||||||
|
uniforms_.scanline_strength = (oversample_ > 1) ? 0.0F : p.scanlines;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SDL3GPUShader::setVSync(bool vsync) {
|
void SDL3GPUShader::setVSync(bool vsync) {
|
||||||
@@ -569,4 +645,68 @@ namespace Rendering {
|
|||||||
integer_scale_ = integer_scale;
|
integer_scale_ = integer_scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// reinitTexturesAndBuffer — recrea scene_texture_ y upload_buffer_ con el
|
||||||
|
// tamaño actual (game × oversample_). No toca pipeline ni samplers.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
auto SDL3GPUShader::reinitTexturesAndBuffer() -> bool {
|
||||||
|
if (device_ == nullptr) { return false; }
|
||||||
|
SDL_WaitForGPUIdle(device_);
|
||||||
|
|
||||||
|
if (scene_texture_ != nullptr) {
|
||||||
|
SDL_ReleaseGPUTexture(device_, scene_texture_);
|
||||||
|
scene_texture_ = nullptr;
|
||||||
|
}
|
||||||
|
if (upload_buffer_ != nullptr) {
|
||||||
|
SDL_ReleaseGPUTransferBuffer(device_, upload_buffer_);
|
||||||
|
upload_buffer_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
tex_width_ = game_width_ * oversample_;
|
||||||
|
tex_height_ = game_height_ * oversample_;
|
||||||
|
uniforms_.screen_height = static_cast<float>(tex_height_);
|
||||||
|
uniforms_.oversample = static_cast<float>(oversample_);
|
||||||
|
|
||||||
|
SDL_GPUTextureCreateInfo tex_info = {};
|
||||||
|
tex_info.type = SDL_GPU_TEXTURETYPE_2D;
|
||||||
|
tex_info.format = SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM;
|
||||||
|
tex_info.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER;
|
||||||
|
tex_info.width = static_cast<Uint32>(tex_width_);
|
||||||
|
tex_info.height = static_cast<Uint32>(tex_height_);
|
||||||
|
tex_info.layer_count_or_depth = 1;
|
||||||
|
tex_info.num_levels = 1;
|
||||||
|
scene_texture_ = SDL_CreateGPUTexture(device_, &tex_info);
|
||||||
|
if (scene_texture_ == nullptr) {
|
||||||
|
SDL_Log("SDL3GPUShader: reinit — failed to create scene texture: %s", SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_GPUTransferBufferCreateInfo tb_info = {};
|
||||||
|
tb_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
|
||||||
|
tb_info.size = static_cast<Uint32>(tex_width_ * tex_height_ * 4);
|
||||||
|
upload_buffer_ = SDL_CreateGPUTransferBuffer(device_, &tb_info);
|
||||||
|
if (upload_buffer_ == nullptr) {
|
||||||
|
SDL_Log("SDL3GPUShader: reinit — failed to create upload buffer: %s", SDL_GetError());
|
||||||
|
SDL_ReleaseGPUTexture(device_, scene_texture_);
|
||||||
|
scene_texture_ = nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Log("SDL3GPUShader: oversample %d → texture %dx%d", oversample_, tex_width_, tex_height_);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Rendering
|
} // namespace Rendering
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ struct PostFXUniforms {
|
|||||||
float bleeding; // 0 = off, 1 = max NTSC chrominance bleeding
|
float bleeding; // 0 = off, 1 = max NTSC chrominance bleeding
|
||||||
float pixel_scale; // physical pixels per logical pixel (vh / tex_height_)
|
float pixel_scale; // physical pixels per logical pixel (vh / tex_height_)
|
||||||
float time; // seconds since SDL init (SDL_GetTicks() / 1000.0f)
|
float time; // seconds since SDL init (SDL_GetTicks() / 1000.0f)
|
||||||
float pad0; // padding — keep struct at 48 bytes (3 × 16)
|
float oversample; // supersampling factor (1.0 = off, 3.0 = 3×SS)
|
||||||
float pad1;
|
float pad1; // padding — keep struct at 48 bytes (3 × 16)
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace Rendering {
|
namespace Rendering {
|
||||||
@@ -60,6 +60,9 @@ namespace Rendering {
|
|||||||
// Activa/desactiva escalado entero (integer scale)
|
// Activa/desactiva escalado entero (integer scale)
|
||||||
void setScaleMode(bool integer_scale) override;
|
void setScaleMode(bool integer_scale) override;
|
||||||
|
|
||||||
|
// Establece factor de supersampling (1 = off, 3 = 3×SS)
|
||||||
|
void setOversample(int factor) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static auto createShaderMSL(SDL_GPUDevice* device,
|
static auto createShaderMSL(SDL_GPUDevice* device,
|
||||||
const char* msl_source,
|
const char* msl_source,
|
||||||
@@ -77,18 +80,24 @@ namespace Rendering {
|
|||||||
Uint32 num_uniform_buffers) -> SDL_GPUShader*;
|
Uint32 num_uniform_buffers) -> SDL_GPUShader*;
|
||||||
|
|
||||||
auto createPipeline() -> bool;
|
auto createPipeline() -> bool;
|
||||||
|
auto reinitTexturesAndBuffer() -> bool; // Recrea textura y buffer con oversample actual
|
||||||
|
|
||||||
SDL_Window* window_ = nullptr;
|
SDL_Window* window_ = nullptr;
|
||||||
SDL_GPUDevice* device_ = nullptr;
|
SDL_GPUDevice* device_ = nullptr;
|
||||||
SDL_GPUGraphicsPipeline* pipeline_ = nullptr;
|
SDL_GPUGraphicsPipeline* pipeline_ = nullptr;
|
||||||
SDL_GPUTexture* scene_texture_ = nullptr;
|
SDL_GPUTexture* scene_texture_ = nullptr;
|
||||||
SDL_GPUTransferBuffer* upload_buffer_ = nullptr;
|
SDL_GPUTransferBuffer* upload_buffer_ = nullptr;
|
||||||
SDL_GPUSampler* sampler_ = nullptr;
|
SDL_GPUSampler* sampler_ = nullptr; // NEAREST — para path sin supersampling
|
||||||
|
SDL_GPUSampler* linear_sampler_ = nullptr; // LINEAR — para path con supersampling
|
||||||
|
|
||||||
PostFXUniforms uniforms_{.vignette_strength = 0.6F, .chroma_strength = 0.15F, .scanline_strength = 0.7F, .screen_height = 192.0F, .pixel_scale = 1.0F};
|
PostFXUniforms uniforms_{.vignette_strength = 0.6F, .chroma_strength = 0.15F, .scanline_strength = 0.7F, .screen_height = 192.0F, .pixel_scale = 1.0F, .oversample = 1.0F};
|
||||||
|
|
||||||
int tex_width_ = 0;
|
int game_width_ = 0; // Dimensiones originales del canvas (sin SS)
|
||||||
|
int game_height_ = 0;
|
||||||
|
int tex_width_ = 0; // Dimensiones de la textura GPU (game × oversample_)
|
||||||
int tex_height_ = 0;
|
int tex_height_ = 0;
|
||||||
|
int oversample_ = 1; // Factor SS actual (1 o 3)
|
||||||
|
float baked_scanline_strength_ = 0.0F; // Guardado para hornear en CPU
|
||||||
bool is_initialized_ = false;
|
bool is_initialized_ = false;
|
||||||
bool vsync_ = true;
|
bool vsync_ = true;
|
||||||
bool integer_scale_ = false;
|
bool integer_scale_ = false;
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ namespace Rendering {
|
|||||||
float gamma = 0.0F; // Corrección gamma (blend 0=off, 1=full)
|
float gamma = 0.0F; // Corrección gamma (blend 0=off, 1=full)
|
||||||
float curvature = 0.0F; // Curvatura barrel CRT
|
float curvature = 0.0F; // Curvatura barrel CRT
|
||||||
float bleeding = 0.0F; // Sangrado de color NTSC
|
float bleeding = 0.0F; // Sangrado de color NTSC
|
||||||
|
bool supersampling{false}; // Supersampling 3×: scanlines horneadas en CPU + sampler LINEAR
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -82,6 +83,13 @@ namespace Rendering {
|
|||||||
*/
|
*/
|
||||||
virtual void setScaleMode(bool /*integer_scale*/) {}
|
virtual void setScaleMode(bool /*integer_scale*/) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Establece el factor de supersampling (1 = off, 3 = 3× SS)
|
||||||
|
* Con factor > 1, la textura GPU se crea a game×factor resolución y
|
||||||
|
* las scanlines se hornean en CPU (uploadPixels). El sampler usa LINEAR.
|
||||||
|
*/
|
||||||
|
virtual void setOversample(int /*factor*/) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Verifica si el backend está usando aceleración por hardware
|
* @brief Verifica si el backend está usando aceleración por hardware
|
||||||
* @return true si usa aceleración (OpenGL/Metal/Vulkan)
|
* @return true si usa aceleración (OpenGL/Metal/Vulkan)
|
||||||
|
|||||||
@@ -718,6 +718,9 @@ namespace Options {
|
|||||||
parseFloatField(p, "gamma", preset.gamma);
|
parseFloatField(p, "gamma", preset.gamma);
|
||||||
parseFloatField(p, "curvature", preset.curvature);
|
parseFloatField(p, "curvature", preset.curvature);
|
||||||
parseFloatField(p, "bleeding", preset.bleeding);
|
parseFloatField(p, "bleeding", preset.bleeding);
|
||||||
|
if (p.contains("supersampling")) {
|
||||||
|
try { preset.supersampling = p["supersampling"].get_value<bool>(); } catch (...) {}
|
||||||
|
}
|
||||||
postfx_presets.push_back(preset);
|
postfx_presets.push_back(preset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -759,6 +762,9 @@ namespace Options {
|
|||||||
file << "# gamma: gamma correction input 2.4 / output 2.2\n";
|
file << "# gamma: gamma correction input 2.4 / output 2.2\n";
|
||||||
file << "# curvature: CRT barrel distortion\n";
|
file << "# curvature: CRT barrel distortion\n";
|
||||||
file << "# bleeding: NTSC horizontal colour bleeding\n";
|
file << "# bleeding: NTSC horizontal colour bleeding\n";
|
||||||
|
file << "# supersampling: 3x internal resolution, scanlines baked in CPU + linear filter\n";
|
||||||
|
file << "# true = consistent 33% scanlines at any zoom (slight softening at non-3x)\n";
|
||||||
|
file << "# false = sharp pixel art, scanlines depend on zoom (33% at 3x, 25% at 4x)\n";
|
||||||
file << "\n";
|
file << "\n";
|
||||||
file << "presets:\n";
|
file << "presets:\n";
|
||||||
file << " - name: \"CRT\"\n";
|
file << " - name: \"CRT\"\n";
|
||||||
@@ -769,6 +775,16 @@ namespace Options {
|
|||||||
file << " gamma: 0.8\n";
|
file << " gamma: 0.8\n";
|
||||||
file << " curvature: 0.0\n";
|
file << " curvature: 0.0\n";
|
||||||
file << " bleeding: 0.0\n";
|
file << " bleeding: 0.0\n";
|
||||||
|
file << " supersampling: false\n";
|
||||||
|
file << " - name: \"CRT-SS\"\n";
|
||||||
|
file << " vignette: 0.6\n";
|
||||||
|
file << " scanlines: 0.7\n";
|
||||||
|
file << " chroma: 0.15\n";
|
||||||
|
file << " mask: 0.6\n";
|
||||||
|
file << " gamma: 0.8\n";
|
||||||
|
file << " curvature: 0.0\n";
|
||||||
|
file << " bleeding: 0.0\n";
|
||||||
|
file << " supersampling: true\n";
|
||||||
file << " - name: \"NTSC\"\n";
|
file << " - name: \"NTSC\"\n";
|
||||||
file << " vignette: 0.4\n";
|
file << " vignette: 0.4\n";
|
||||||
file << " scanlines: 0.5\n";
|
file << " scanlines: 0.5\n";
|
||||||
@@ -777,6 +793,7 @@ namespace Options {
|
|||||||
file << " gamma: 0.5\n";
|
file << " gamma: 0.5\n";
|
||||||
file << " curvature: 0.0\n";
|
file << " curvature: 0.0\n";
|
||||||
file << " bleeding: 0.6\n";
|
file << " bleeding: 0.6\n";
|
||||||
|
file << " supersampling: false\n";
|
||||||
file << " - name: \"CURVED\"\n";
|
file << " - name: \"CURVED\"\n";
|
||||||
file << " vignette: 0.5\n";
|
file << " vignette: 0.5\n";
|
||||||
file << " scanlines: 0.6\n";
|
file << " scanlines: 0.6\n";
|
||||||
@@ -785,6 +802,7 @@ namespace Options {
|
|||||||
file << " gamma: 0.7\n";
|
file << " gamma: 0.7\n";
|
||||||
file << " curvature: 0.8\n";
|
file << " curvature: 0.8\n";
|
||||||
file << " bleeding: 0.0\n";
|
file << " bleeding: 0.0\n";
|
||||||
|
file << " supersampling: false\n";
|
||||||
file << " - name: \"SCANLINES\"\n";
|
file << " - name: \"SCANLINES\"\n";
|
||||||
file << " vignette: 0.0\n";
|
file << " vignette: 0.0\n";
|
||||||
file << " scanlines: 0.8\n";
|
file << " scanlines: 0.8\n";
|
||||||
@@ -793,6 +811,7 @@ namespace Options {
|
|||||||
file << " gamma: 0.0\n";
|
file << " gamma: 0.0\n";
|
||||||
file << " curvature: 0.0\n";
|
file << " curvature: 0.0\n";
|
||||||
file << " bleeding: 0.0\n";
|
file << " bleeding: 0.0\n";
|
||||||
|
file << " supersampling: false\n";
|
||||||
file << " - name: \"SUBTLE\"\n";
|
file << " - name: \"SUBTLE\"\n";
|
||||||
file << " vignette: 0.3\n";
|
file << " vignette: 0.3\n";
|
||||||
file << " scanlines: 0.4\n";
|
file << " scanlines: 0.4\n";
|
||||||
@@ -801,6 +820,7 @@ namespace Options {
|
|||||||
file << " gamma: 0.3\n";
|
file << " gamma: 0.3\n";
|
||||||
file << " curvature: 0.0\n";
|
file << " curvature: 0.0\n";
|
||||||
file << " bleeding: 0.0\n";
|
file << " bleeding: 0.0\n";
|
||||||
|
file << " supersampling: false\n";
|
||||||
|
|
||||||
file.close();
|
file.close();
|
||||||
|
|
||||||
|
|||||||
@@ -124,6 +124,7 @@ namespace Options {
|
|||||||
float gamma{0.0F}; // Corrección gamma input 2.4 / output 2.2 (0.0 = off, 1.0 = plena)
|
float gamma{0.0F}; // Corrección gamma input 2.4 / output 2.2 (0.0 = off, 1.0 = plena)
|
||||||
float curvature{0.0F}; // Distorsión barrel CRT (0.0 = plana, 1.0 = máxima curvatura)
|
float curvature{0.0F}; // Distorsión barrel CRT (0.0 = plana, 1.0 = máxima curvatura)
|
||||||
float bleeding{0.0F}; // Sangrado de color NTSC horizontal Y/C (0.0 = off, 1.0 = máximo)
|
float bleeding{0.0F}; // Sangrado de color NTSC horizontal Y/C (0.0 = off, 1.0 = máximo)
|
||||||
|
bool supersampling{false}; // 3x supersampling: scanlines horneadas en CPU + sampler LINEAR
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- Variables globales ---
|
// --- Variables globales ---
|
||||||
|
|||||||
Reference in New Issue
Block a user