Thank you so much! I had forgotten about the preset files entirely.
I rewrote the shader file to remove the “gap height” parameter, and just operate off of the standard SourceSize
/ OutputSize
arguments. It adds lines right in the center of the image—however many are needed to bring the source height up to equal the output height. This way, the separation can be controlled from the absolute Y scale in the playlist file, without editing the shader file. Making an X version for side-by-side/rotated content should be easy.
I reverted the vertex shader to stock, and moved all the hard work to the pixel-shader step. The logic was easier than I expected. I don’t have any test patterns to to run (no 240p test suite for DS) but I think it’s pixel-perfect.
I have to run a pass of box-max.slang
(or some other auto-box shader) to preserve the aspect ratio. Without that, the image gets vertically squashed down to the original aspect ratio. {1}
Playlist file
shaders = "2"
shader0 = "shaders/splitScreenV.slang"
shader1 = "../auto-box/box-max.slang"
scale_type_x0 = "source"
scale_x0 = 1.0
scale_type_y0 = "absolute"
scale_y0 = 448 // (192px * 2) + 64px
scale_type1 = viewport
scale1 = 1.0
Shader file (splitScreenV.slang)
#version 450
// Split Screen Vertical
// Changes an image to a taller resolution by inserting a monochromatic bar in
// the center of the image. Aspect and resolution of the top and bottom
// "screens" is not affected.
// Intended for use with Nintendo DS video.
// Control the size of the gap by modifying the output Y scale in the shader playlist.
layout(std140, set = 0, binding = 0) uniform UBO
{
vec4 SourceSize;
vec4 OutputSize;
mat4 MVP;
float mask_r;
float mask_g;
float mask_b;
} global;
#pragma parameter mask_r "Mask color (R)" 0.0 0.0 1.0 0.00390625
#pragma parameter mask_g "Mask color (G)" 0.0 0.0 1.0 0.00390625
#pragma parameter mask_b "Mask color (B)" 0.0 0.0 1.0 0.00390625
#pragma stage vertex
layout(location = 0) in vec4 Position;
layout(location = 1) in vec2 TexCoord;
layout(location = 0) out vec2 vTexCoord;
void main()
{
gl_Position = global.MVP * Position;
vTexCoord = TexCoord; // null vertex stage
}
#pragma stage fragment
layout(location = 0) in vec2 vTexCoord;
layout(location = 0) out vec4 FragColor;
layout(set = 0, binding = 2) uniform sampler2D Source;
void main()
{
float oldH_px = global.SourceSize.y;
float newH_px = global.OutputSize.y;
float gap_px = newH_px - oldH_px;
float topScrEdge = (oldH_px / 2) / newH_px; // bottom edge of top screen, normalized
float botScrEdge = 1.0 - topScrEdge; // top edge of bottom screen, normalized
float scrH = topScrEdge; // height of each screen, normalized (just for code clarity)
if (vTexCoord.y < topScrEdge) { // top screen
float topScrY = vTexCoord.y / scrH; // pos within top screen
float y = topScrY / 2; // pos in source img
vec2 coord = vec2(vTexCoord.x, y);
FragColor = texture(Source, coord);
} else if (vTexCoord.y > botScrEdge) { // bottom screen
float botScrY = (vTexCoord.y - botScrEdge) / scrH; // pos within bottom screen
float y = (botScrY / 2) + 0.5; // pos in source img
vec2 coord = vec2(vTexCoord.x, y);
FragColor = texture(Source, coord);
} else { // screen gap
FragColor = vec4(global.mask_r, global.mask_g, global.mask_b, 1);
}
}
There is one remaining issue, which is that “margins” are still added to the image with this method. For instance, this is the smallest window that can display a 1x-scaled output:
In this example, the window is exactly 512x768px, which is an exact 2x scale of the native DS resolution with no gap, as output by the core (256x384px). It appears that the absolute
scaling mode just scales the source framebuffer up by an integer, then zooms/centers the output in this “canvas”. So I guess the original aspect ratio is kind of inescapable, huh? I can’t help but question this—doesn’t exactly seem like the expected behavior, and it’s clearly limiting.
Integer scale overscale sometimes fixes this, but not always, and sometimes actually overscales the image, so it’s only a workaround if you’re lucky.
Still, I’m really happy with the progress here. It might be possible to make this work with a custom viewport. Thanks again for your help so far—let me know if you see a solution for the issue above.
{1} Side note: border shaders
Some (maybe all) of the “border” shaders (e.g. sgb.slangp
) skip this step, so their aspect ratio is actually incorrect! This also causes non-integer scaling—check out the pixels in the screencap below. However, this spares these shaders from the “margins” described above.
If the absolute
scaling mode actually sized the output framebuffer to that exact size, I think these issues would all go away.
shaders = "1"
shader0 = "../shaders/imgborder-sgb.slang"
scale_type_x0 = "absolute"
scale_x0 = "256"
scale_type_y0 = "absolute"
scale_y0 = "224"
parameters = "box_scale;location;in_res_x;in_res_y"
box_scale = "1.000000"
location = "0.500000"
in_res_x = "160.000000"
in_res_y = "144.000000"
textures = "BORDER"
BORDER = "sgb.png"