fix: el supersampling es feia per cpu en lloc de per gpu

This commit is contained in:
2026-03-25 21:33:06 +01:00
parent 3493636954
commit 1dbfff2c17
5 changed files with 247 additions and 82 deletions

View File

@@ -9,6 +9,7 @@
#ifndef __APPLE__
#include "core/rendering/sdl3gpu/postfx_frag_spv.h"
#include "core/rendering/sdl3gpu/postfx_vert_spv.h"
#include "core/rendering/sdl3gpu/upscale_frag_spv.h"
#endif
#ifdef __APPLE__
@@ -176,6 +177,17 @@ fragment float4 postfx_fs(PostVOut in [[stage_in]],
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);
}
)";
// NOLINTEND(readability-identifier-naming)
#endif // __APPLE__
@@ -211,9 +223,7 @@ namespace Rendering {
SDL_GetTextureSize(texture, &fw, &fh);
game_width_ = static_cast<int>(fw);
game_height_ = static_cast<int>(fh);
tex_width_ = game_width_ * oversample_;
tex_height_ = game_height_ * oversample_;
uniforms_.screen_height = static_cast<float>(tex_height_); // Altura de la textura GPU
uniforms_.screen_height = static_cast<float>(game_height_);
uniforms_.oversample = static_cast<float>(oversample_);
// ----------------------------------------------------------------
@@ -245,15 +255,15 @@ namespace Rendering {
}
// ----------------------------------------------------------------
// 3. Create scene texture (upload target + sampler source)
// 3. Create scene texture (upload target, always game resolution)
// Format: B8G8R8A8_UNORM matches SDL ARGB8888 byte layout on LE
// ----------------------------------------------------------------
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.width = static_cast<Uint32>(game_width_);
tex_info.height = static_cast<Uint32>(game_height_);
tex_info.layer_count_or_depth = 1;
tex_info.num_levels = 1;
scene_texture_ = SDL_CreateGPUTexture(device_, &tex_info);
@@ -264,11 +274,31 @@ namespace Rendering {
}
// ----------------------------------------------------------------
// 4. Create upload transfer buffer (CPU → GPU, size = w*h*4 bytes)
// 3b. Create scaled texture (render target for upscale pass, only with SS)
// ----------------------------------------------------------------
if (oversample_ > 1) {
SDL_GPUTextureCreateInfo scaled_info = {};
scaled_info.type = SDL_GPU_TEXTURETYPE_2D;
scaled_info.format = SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM;
scaled_info.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER | SDL_GPU_TEXTUREUSAGE_COLOR_TARGET;
scaled_info.width = static_cast<Uint32>(game_width_ * oversample_);
scaled_info.height = static_cast<Uint32>(game_height_ * oversample_);
scaled_info.layer_count_or_depth = 1;
scaled_info.num_levels = 1;
scaled_texture_ = SDL_CreateGPUTexture(device_, &scaled_info);
if (scaled_texture_ == nullptr) {
SDL_Log("SDL3GPUShader: failed to create scaled texture: %s", SDL_GetError());
cleanup();
return false;
}
}
// ----------------------------------------------------------------
// 4. Create upload transfer buffer (CPU → GPU, always game resolution)
// ----------------------------------------------------------------
SDL_GPUTransferBufferCreateInfo tb_info = {};
tb_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
tb_info.size = static_cast<Uint32>(tex_width_ * tex_height_ * 4);
tb_info.size = static_cast<Uint32>(game_width_ * game_height_ * 4);
upload_buffer_ = SDL_CreateGPUTransferBuffer(device_, &tb_info);
if (upload_buffer_ == nullptr) {
SDL_Log("SDL3GPUShader: failed to create upload buffer: %s", SDL_GetError());
@@ -316,7 +346,7 @@ namespace Rendering {
}
is_initialized_ = true;
SDL_Log("SDL3GPUShader: initialized OK (%dx%d)", tex_width_, tex_height_);
SDL_Log("SDL3GPUShader: initialized OK — game %dx%d, oversample %d", game_width_, game_height_, oversample_);
return true;
}
@@ -326,6 +356,7 @@ namespace Rendering {
auto SDL3GPUShader::createPipeline() -> bool {
const SDL_GPUTextureFormat SWAPCHAIN_FMT = SDL_GetGPUSwapchainTextureFormat(device_, window_);
// ---- PostFX pipeline (scene/scaled → swapchain) ----
#ifdef __APPLE__
SDL_GPUShader* vert = createShaderMSL(device_, POSTFX_VERT_MSL, "postfx_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
SDL_GPUShader* frag = createShaderMSL(device_, POSTFX_FRAG_MSL, "postfx_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
@@ -365,9 +396,48 @@ namespace Rendering {
SDL_ReleaseGPUShader(device_, frag);
if (pipeline_ == nullptr) {
SDL_Log("SDL3GPUShader: pipeline creation failed: %s", SDL_GetError());
SDL_Log("SDL3GPUShader: PostFX pipeline creation failed: %s", SDL_GetError());
return false;
}
// ---- Upscale pipeline (scene → scaled_texture_, nearest) ----
#ifdef __APPLE__
SDL_GPUShader* uvert = createShaderMSL(device_, POSTFX_VERT_MSL, "postfx_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
SDL_GPUShader* ufrag = createShaderMSL(device_, UPSCALE_FRAG_MSL, "upscale_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0);
#else
SDL_GPUShader* uvert = createShaderSPIRV(device_, kpostfx_vert_spv, kpostfx_vert_spv_size, "main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
SDL_GPUShader* ufrag = createShaderSPIRV(device_, kupscale_frag_spv, kupscale_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0);
#endif
if ((uvert == nullptr) || (ufrag == nullptr)) {
SDL_Log("SDL3GPUShader: failed to compile upscale shaders");
if (uvert != nullptr) { SDL_ReleaseGPUShader(device_, uvert); }
if (ufrag != nullptr) { SDL_ReleaseGPUShader(device_, ufrag); }
return false;
}
SDL_GPUColorTargetDescription upscale_color_target = {};
upscale_color_target.format = SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM;
upscale_color_target.blend_state = no_blend;
SDL_GPUGraphicsPipelineCreateInfo upscale_pipe_info = {};
upscale_pipe_info.vertex_shader = uvert;
upscale_pipe_info.fragment_shader = ufrag;
upscale_pipe_info.vertex_input_state = no_input;
upscale_pipe_info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST;
upscale_pipe_info.target_info.num_color_targets = 1;
upscale_pipe_info.target_info.color_target_descriptions = &upscale_color_target;
upscale_pipeline_ = SDL_CreateGPUGraphicsPipeline(device_, &upscale_pipe_info);
SDL_ReleaseGPUShader(device_, uvert);
SDL_ReleaseGPUShader(device_, ufrag);
if (upscale_pipeline_ == nullptr) {
SDL_Log("SDL3GPUShader: upscale pipeline creation failed: %s", SDL_GetError());
return false;
}
return true;
}
@@ -385,43 +455,8 @@ namespace Rendering {
return;
}
if (oversample_ <= 1) {
// Path sin supersampling: copia directa
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(1.0, 0.42, scanline_strength).
auto* out = static_cast<Uint32*>(mapped);
const int OS = oversample_;
const float BRIGHT_MUL = 1.0F; // rows 0..OS-2 (bright = 1.0×, sin amplificación)
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;
}
}
}
}
}
// 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_);
}
@@ -438,25 +473,44 @@ namespace Rendering {
return;
}
// ---- Copy pass: transfer buffer → scene texture ----
// ---- Copy pass: transfer buffer → scene texture (siempre a resolución del juego) ----
SDL_GPUCopyPass* copy = SDL_BeginGPUCopyPass(cmd);
if (copy != nullptr) {
SDL_GPUTextureTransferInfo src = {};
src.transfer_buffer = upload_buffer_;
src.offset = 0;
src.pixels_per_row = static_cast<Uint32>(tex_width_);
src.rows_per_layer = static_cast<Uint32>(tex_height_);
src.pixels_per_row = static_cast<Uint32>(game_width_);
src.rows_per_layer = static_cast<Uint32>(game_height_);
SDL_GPUTextureRegion dst = {};
dst.texture = scene_texture_;
dst.w = static_cast<Uint32>(tex_width_);
dst.h = static_cast<Uint32>(tex_height_);
dst.w = static_cast<Uint32>(game_width_);
dst.h = static_cast<Uint32>(game_height_);
dst.d = 1;
SDL_UploadToGPUTexture(copy, &src, &dst, false);
SDL_EndGPUCopyPass(copy);
}
// ---- Upscale pass: scene_texture_ (game res) → scaled_texture_ (game × OS) ----
if (oversample_ > 1 && scaled_texture_ != nullptr && upscale_pipeline_ != nullptr) {
SDL_GPUColorTargetInfo upscale_target = {};
upscale_target.texture = scaled_texture_;
upscale_target.load_op = SDL_GPU_LOADOP_DONT_CARE;
upscale_target.store_op = SDL_GPU_STOREOP_STORE;
SDL_GPURenderPass* upass = SDL_BeginGPURenderPass(cmd, &upscale_target, 1, nullptr);
if (upass != nullptr) {
SDL_BindGPUGraphicsPipeline(upass, upscale_pipeline_);
SDL_GPUTextureSamplerBinding ubinding = {};
ubinding.texture = scene_texture_;
ubinding.sampler = sampler_; // NEAREST
SDL_BindGPUFragmentSamplers(upass, 0, &ubinding, 1);
SDL_DrawGPUPrimitives(upass, 3, 1, 0, 0);
SDL_EndGPURenderPass(upass);
}
}
// ---- Acquire swapchain texture ----
SDL_GPUTexture* swapchain = nullptr;
Uint32 sw = 0;
@@ -506,22 +560,28 @@ namespace Rendering {
SDL_GPUViewport vp = {.x = vx, .y = vy, .w = vw, .h = vh, .min_depth = 0.0F, .max_depth = 1.0F};
SDL_SetGPUViewport(pass, &vp);
// pixel_scale: pixels físicos por pixel lógico de juego (para scanlines sin SS).
// Con SS las scanlines están horneadas en CPU → scanline_strength=0 → no se usa.
uniforms_.pixel_scale = (game_height_ > 0)
? (vh / static_cast<float>(game_height_))
: 1.0F;
// pixel_scale: subpíxeles de textura por pixel lógico de juego.
// Sin SS: vh/game_height (zoom de ventana = subpíxeles por fila).
// Con SS: la textura entrada al PostFX tiene OS subfilas por fila lógica,
// así que pixel_scale = OS (independiente del zoom de ventana).
uniforms_.pixel_scale = (oversample_ > 1)
? static_cast<float>(oversample_)
: ((game_height_ > 0) ? (vh / static_cast<float>(game_height_)) : 1.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.
// Con SS: leer de scaled_texture_ (ya ampliada); con LINEAR para suavizar
// el escalado final a zooms no-múltiplo-de-OS.
// Sin SS: leer de scene_texture_ con 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 = scene_texture_;
binding.texture = input_texture;
binding.sampler = active_sampler;
SDL_BindGPUFragmentSamplers(pass, 0, &binding, 1);
@@ -547,10 +607,18 @@ namespace Rendering {
SDL_ReleaseGPUGraphicsPipeline(device_, pipeline_);
pipeline_ = nullptr;
}
if (upscale_pipeline_ != nullptr) {
SDL_ReleaseGPUGraphicsPipeline(device_, upscale_pipeline_);
upscale_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 (upload_buffer_ != nullptr) {
SDL_ReleaseGPUTransferBuffer(device_, upload_buffer_);
upload_buffer_ = nullptr;
@@ -638,10 +706,8 @@ namespace Rendering {
uniforms_.bleeding = p.bleeding;
uniforms_.flicker = p.flicker;
// 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;
// Las scanlines siempre las aplica el shader PostFX en GPU.
uniforms_.scanline_strength = p.scanlines;
}
void SDL3GPUShader::setVSync(bool vsync) {
@@ -669,8 +735,8 @@ namespace Rendering {
}
// ---------------------------------------------------------------------------
// reinitTexturesAndBuffer — recrea scene_texture_ y upload_buffer_ con el
// tamaño actual (game × oversample_). No toca pipeline ni samplers.
// reinitTexturesAndBuffer — recrea scene_texture_, scaled_texture_ y
// upload_buffer_ con el factor oversample_ actual. No toca pipelines ni samplers.
// ---------------------------------------------------------------------------
auto SDL3GPUShader::reinitTexturesAndBuffer() -> bool {
if (device_ == nullptr) { return false; }
@@ -680,22 +746,25 @@ namespace Rendering {
SDL_ReleaseGPUTexture(device_, scene_texture_);
scene_texture_ = nullptr;
}
if (scaled_texture_ != nullptr) {
SDL_ReleaseGPUTexture(device_, scaled_texture_);
scaled_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_.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;
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.width = static_cast<Uint32>(game_width_);
tex_info.height = static_cast<Uint32>(game_height_);
tex_info.layer_count_or_depth = 1;
tex_info.num_levels = 1;
scene_texture_ = SDL_CreateGPUTexture(device_, &tex_info);
@@ -704,18 +773,41 @@ namespace Rendering {
return false;
}
// scaled_texture_: solo con SS
if (oversample_ > 1) {
SDL_GPUTextureCreateInfo scaled_info = {};
scaled_info.type = SDL_GPU_TEXTURETYPE_2D;
scaled_info.format = SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM;
scaled_info.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER | SDL_GPU_TEXTUREUSAGE_COLOR_TARGET;
scaled_info.width = static_cast<Uint32>(game_width_ * oversample_);
scaled_info.height = static_cast<Uint32>(game_height_ * oversample_);
scaled_info.layer_count_or_depth = 1;
scaled_info.num_levels = 1;
scaled_texture_ = SDL_CreateGPUTexture(device_, &scaled_info);
if (scaled_texture_ == nullptr) {
SDL_Log("SDL3GPUShader: reinit — failed to create scaled texture: %s", SDL_GetError());
SDL_ReleaseGPUTexture(device_, scene_texture_);
scene_texture_ = nullptr;
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>(tex_width_ * tex_height_ * 4);
tb_info.size = static_cast<Uint32>(game_width_ * game_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());
if (scaled_texture_ != nullptr) { SDL_ReleaseGPUTexture(device_, scaled_texture_); scaled_texture_ = nullptr; }
SDL_ReleaseGPUTexture(device_, scene_texture_);
scene_texture_ = nullptr;
return false;
}
SDL_Log("SDL3GPUShader: oversample %d → texture %dx%d", oversample_, tex_width_, tex_height_);
SDL_Log("SDL3GPUShader: oversample %d — scene %dx%d, scaled %dx%d",
oversample_, game_width_, game_height_,
game_width_ * oversample_, game_height_ * oversample_);
return true;
}

View File

@@ -84,20 +84,19 @@ namespace Rendering {
SDL_Window* window_ = nullptr;
SDL_GPUDevice* device_ = nullptr;
SDL_GPUGraphicsPipeline* pipeline_ = nullptr;
SDL_GPUTexture* scene_texture_ = nullptr;
SDL_GPUGraphicsPipeline* pipeline_ = nullptr; // PostFX pass
SDL_GPUGraphicsPipeline* upscale_pipeline_ = nullptr; // Upscale nearest pass (solo con SS)
SDL_GPUTexture* scene_texture_ = nullptr; // Canvas del juego (game_width_ × game_height_)
SDL_GPUTexture* scaled_texture_ = nullptr; // Render target intermedio (game × OS), solo con SS
SDL_GPUTransferBuffer* upload_buffer_ = nullptr;
SDL_GPUSampler* sampler_ = nullptr; // NEAREST — para path sin supersampling
SDL_GPUSampler* linear_sampler_ = nullptr; // LINEAR — para path con supersampling
SDL_GPUSampler* sampler_ = nullptr; // NEAREST
SDL_GPUSampler* linear_sampler_ = nullptr; // LINEAR
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 game_width_ = 0; // Dimensiones originales del canvas (sin SS)
int game_width_ = 0; // Dimensiones originales del canvas
int game_height_ = 0;
int tex_width_ = 0; // Dimensiones de la textura GPU (game × oversample_)
int tex_height_ = 0;
int oversample_ = 1; // Factor SS actual (1 o 3)
float baked_scanline_strength_ = 0.0F; // Guardado para hornear en CPU
int oversample_ = 1; // Factor SS actual (1 o 3)
bool is_initialized_ = false;
bool vsync_ = true;
bool integer_scale_ = false;

View File

@@ -0,0 +1,59 @@
#pragma once
#include <cstddef>
#include <cstdint>
static const uint8_t kupscale_frag_spv[] = {
0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x0b, 0x00, 0x0d, 0x00,
0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00,
0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00,
0x47, 0x4c, 0x53, 0x4c, 0x2e, 0x73, 0x74, 0x64, 0x2e, 0x34, 0x35, 0x30,
0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x07, 0x00, 0x04, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, 0x00, 0x00, 0x00, 0x00,
0x09, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x10, 0x00, 0x03, 0x00,
0x04, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00,
0x02, 0x00, 0x00, 0x00, 0xc2, 0x01, 0x00, 0x00, 0x04, 0x00, 0x0a, 0x00,
0x47, 0x4c, 0x5f, 0x47, 0x4f, 0x4f, 0x47, 0x4c, 0x45, 0x5f, 0x63, 0x70,
0x70, 0x5f, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x5f, 0x6c, 0x69, 0x6e, 0x65,
0x5f, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x00, 0x00,
0x04, 0x00, 0x08, 0x00, 0x47, 0x4c, 0x5f, 0x47, 0x4f, 0x4f, 0x47, 0x4c,
0x45, 0x5f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x64, 0x69,
0x72, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x00, 0x05, 0x00, 0x04, 0x00,
0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, 0x00, 0x00, 0x00, 0x00,
0x05, 0x00, 0x05, 0x00, 0x09, 0x00, 0x00, 0x00, 0x6f, 0x75, 0x74, 0x5f,
0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x00, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00,
0x0d, 0x00, 0x00, 0x00, 0x73, 0x63, 0x65, 0x6e, 0x65, 0x00, 0x00, 0x00,
0x05, 0x00, 0x04, 0x00, 0x11, 0x00, 0x00, 0x00, 0x76, 0x5f, 0x75, 0x76,
0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00,
0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00,
0x0d, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x47, 0x00, 0x04, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x11, 0x00, 0x00, 0x00,
0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x02, 0x00,
0x02, 0x00, 0x00, 0x00, 0x21, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x16, 0x00, 0x03, 0x00, 0x06, 0x00, 0x00, 0x00,
0x20, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00,
0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00,
0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
0x3b, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x19, 0x00, 0x09, 0x00, 0x0a, 0x00, 0x00, 0x00,
0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x03, 0x00, 0x0b, 0x00, 0x00, 0x00,
0x0a, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x0c, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00,
0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x17, 0x00, 0x04, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00,
0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x36, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00,
0x05, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x0b, 0x00, 0x00, 0x00,
0x0e, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00,
0x0f, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
0x57, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00,
0x0e, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00,
0x09, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x01, 0x00,
0x38, 0x00, 0x01, 0x00,
};
static const size_t kupscale_frag_spv_size = 628;