libretro/glsl-shaders

Cocktail cabinet - 4 player split screen

JKlessens opened this issue · 7 comments

Hi,

I love the work you guys have already done, especially the cocktail cabinet shader. I am building a gaming table for 4 player on 4 sides of the table. And as I will be using only 1 screen it would be very nice if I could somehow split the screen up into 4 player rotated splitscreen. Something like this (don't know how to add an image):

------------------------------------
| -------------------------------- |
||            ||       2          ||
||     3      ||                  ||
||            | -----------------  |
| ------------------|             ||
||                  ||     4      ||
||            1     ||            ||
------------------------------------

Or this might also work, as the screen is widescreen (16:9):

------------------------------------------
| --------------------------------------||
||          ||        2      |          ||
||          ||               |          ||
||     3    || --------------|    4     ||
||          ||               |          ||
||          ||        1      |          ||
------------------------------------------

If possible I would like to request a shader like that. Or maybe some guidelines how to edit the existing cabinet shaders, to this end, so I can edit them myself. I have software engineering experience, I just don't have any experience with shaders.

edit: An extra shader for 3 player split screen would also be nice to have, but not specifically necessary if we already have a 4 player one.

Thanks for any help you can provide.

I've spent some time thinking about this and I'm not sure how it could be done at 16:9 aspect ratio without seriously distorting the images or leaving a lot of black space. The second diagram could potentially work on an "ultrawide" monitor, though, like 21:9, I suppose.

Thank you, for the reply.
Not just to convince you to make such a shader or help me, but also for the sake of discussion (and partly to convince you ;) ). I think even on a 16:9 screen the second option might work. I did some quick calculations, assuming a 1920x1080 screen, which, I think, can be assumed. And what I've come up with are the following resolutions for the different sections, that should at least fit:

903 x 508 (16:9 sections)
720 x 540 (4:3 sections)

I am a bit unsure what the desired aspect ratio of the sections should be and/or how the current shader handles different aspect ratios, and whether it is possible to maybe even change the size of the different sections, based on the aspect ratio of the original image. But even if that is not possible, the 4:3 ratio would also fit in the 16:9 sections (677x508), it would just leave more black space.

I understand there will always be black borders/space, but that is not a deal breaker right? The 2 player cabinet (portrait) also leaves black space on a 16:9 screen. Also I understand there might be some loss in visual fidelity, but although these sections would be pretty small, the resolution is still not that far off from, or even better than most oldschool native resolutions. SNES had a max resolution of 512x448 for example. And the N64 had 4 player split screen with a max resolution of 720×576 for the full screen. Classic arcade games have even less I think.

Well, here's the best I could do with a slang shader (glsl shaders for this sort of thing are much more complicated due to supporting NPOT textures):

#version 450

layout(push_constant) uniform Push
{
	vec4 SourceSize;
	vec4 OriginalSize;
	vec4 OutputSize;
	uint FrameCount;
} params;


// leaving these here because they're helpful in identifying what numbers you're messing with later
const float height = 1.0;
const float width = 1.0;
const float location = 0.0001;

layout(std140, set = 0, binding = 0) uniform UBO
{
	mat4 MVP;
} global;

#pragma stage vertex
layout(location = 0) in vec4 Position;
layout(location = 1) in vec2 TexCoord;
layout(location = 0) out vec2 vTexCoord;
layout(location = 1) out vec2 vTexCoord1;
layout(location = 2) out vec2 vTexCoord2;
layout(location = 3) out vec2 vTexCoord3;

void main()
{
   gl_Position = global.MVP * Position;
   // left
   vTexCoord  = ((TexCoord.xy - vec2(0.5 - (location), 0.5)) * mat2x2(0.0,  1.0001 * (width * 1.4), -3.2501 * (height), 0.0)) + vec2(0.5, 0. ) - vec2(0., 0.6015);
   // right
   vTexCoord1 = ((TexCoord.xy - vec2(0.5 + (location), 0.5)) * mat2x2(0.0, -1.0001 * (width * 1.4),  3.2501 * (height), 0.0)) + vec2(0.5, 0. ) - vec2(0., 0.6015);
   // top
   vTexCoord2 = ((TexCoord.xy - vec2(0.5 + (location), 0.5)) * mat2x2(1.0001 * (width * 2.7001), 0.0, 0.0, 2.001 * (height))) + vec2(0.5, 0. ) - vec2(0., 0.0);
   // bottom
   vTexCoord3 = ((TexCoord.xy - vec2(0.5 + (location), 0.5)) * mat2x2(-1.0001 * (width * 2.7001), 0.0, 0.0, -2.001 * (height))) + vec2(0.5, 0. ) - vec2(0., 0.);
}

#pragma stage fragment
layout(location = 0) in vec2 vTexCoord;
layout(location = 1) in vec2 vTexCoord1;
layout(location = 2) in vec2 vTexCoord2;
layout(location = 3) in vec2 vTexCoord3;
layout(location = 0) out vec4 FragColor;
layout(set = 0, binding = 2) uniform sampler2D Source;

vec2 sharpen(vec2 coord){
	vec2 p = coord.xy;

	p = p * params.SourceSize.xy + vec2(0.5, 0.5);

	vec2 i = floor(p);
	vec2 f = p - i;
	f = f * f * f * (f * (f * 6.0 - vec2(15.0, 15.0)) + vec2(10.0, 10.0));
	p = i + f;

	p = (p - vec2(0.5, 0.5)) * params.SourceSize.zw;
	return p;
}

void main()
{
   FragColor = vec4(texture(Source, sharpen(vTexCoord)).rgb, 1.0) + vec4(texture(Source, sharpen(vTexCoord1)).rgb, 1.0) + vec4(texture(Source, sharpen(vTexCoord2)).rgb, 1.0) + vec4(texture(Source, sharpen(vTexCoord3)).rgb, 1.0) ;
}

Looks like this:
image

Oh that looks nice. I will try it out, when I get home! Thank you very much.

Hi,
I just tested it on my retropie, but it seems like some things at the edges of the images are bleeding through into the rest of the screen. See screenshots below:

edit: Just FYI, I had to change the video driver to glcore to get retroarch to recognize the .slang shader, as regular gl does not support slang shaders. And Vulkan is not available on retropie.

Street Fighter (NeoGeo):
PXL_20201012_223852505

Super Mario (SNES):
PXL_20201012_224648616

I also tried Mame, and it was the same.

Any idea why this might be happening. For as far as I can tell it works perfectly when most of the screen is black. And looking at your screenshot it was working nicely for you as well.

Ah, right. Yeah, GLES has trouble with clamping coords for some reason. Try this one, which manually clamps them:

#version 450

layout(push_constant) uniform Push
{
	vec4 SourceSize;
	vec4 OriginalSize;
	vec4 OutputSize;
	uint FrameCount;
} params;


// leaving these here because they're helpful in identifying what numbers you're messing with later
const float height = 1.0;
const float width = 1.0;
const float location = 0.0001;

layout(std140, set = 0, binding = 0) uniform UBO
{
	mat4 MVP;
} global;

#pragma stage vertex
layout(location = 0) in vec4 Position;
layout(location = 1) in vec2 TexCoord;
layout(location = 0) out vec2 vTexCoord;
layout(location = 1) out vec2 vTexCoord1;
layout(location = 2) out vec2 vTexCoord2;
layout(location = 3) out vec2 vTexCoord3;

void main()
{
   gl_Position = global.MVP * Position;
   // left
   vTexCoord  = ((TexCoord.xy * 1.00001 - vec2(0.5 - (location), 0.5)) * mat2x2(0.0,  1.0001 * (width * 1.4), -3.2501 * (height), 0.0)) + vec2(0.5, 0. ) - vec2(0., 0.6015);
   // right
   vTexCoord1 = ((TexCoord.xy * 1.00001 - vec2(0.5 + (location), 0.5)) * mat2x2(0.0, -1.0001 * (width * 1.4),  3.2501 * (height), 0.0)) + vec2(0.5, 0. ) - vec2(0., 0.6015);
   // top
   vTexCoord2 = ((TexCoord.xy * 1.00001 - vec2(0.5 + (location), 0.5)) * mat2x2(1.0001 * (width * 2.7001), 0.0, 0.0, 2.001 * (height))) + vec2(0.5, 0. ) - vec2(0., 0.0);
   // bottom
   vTexCoord3 = ((TexCoord.xy * 1.00001 - vec2(0.5 + (location), 0.5)) * mat2x2(-1.0001 * (width * 2.7001), 0.0, 0.0, -2.001 * (height))) + vec2(0.5, 0. ) - vec2(0., 0.);
}

#pragma stage fragment
layout(location = 0) in vec2 vTexCoord;
layout(location = 1) in vec2 vTexCoord1;
layout(location = 2) in vec2 vTexCoord2;
layout(location = 3) in vec2 vTexCoord3;
layout(location = 0) out vec4 FragColor;
layout(set = 0, binding = 2) uniform sampler2D Source;

vec2 sharpen(vec2 coord){
	vec2 p = coord.xy;

	p = p * params.SourceSize.xy + vec2(0.5, 0.5);

	vec2 i = floor(p);
	vec2 f = p - i;
	f = f * f * f * (f * (f * 6.0 - vec2(15.0, 15.0)) + vec2(10.0, 10.0));
	p = i + f;

	p = (p - vec2(0.5, 0.5)) * params.SourceSize.zw;
	return p;
}

float clampfix(vec2 coord){
   return float(coord.x > 0.) * float(coord.y > 0.) * float(coord.x <1.) * float(coord.y < 1.);
}

void main()
{
   FragColor = vec4(texture(Source, sharpen(vTexCoord)).rgb, 1.0) * clampfix(vTexCoord) + vec4(texture(Source, sharpen(vTexCoord1)).rgb, 1.0) * clampfix(vTexCoord1) + vec4(texture(Source, sharpen(vTexCoord2)).rgb, 1.0) * clampfix(vTexCoord2) + vec4(texture(Source, sharpen(vTexCoord3)).rgb, 1.0) * clampfix(vTexCoord3);
}

Also, make sure you set your filtering to "linear" to avoid ugly uneven pixel sizes.

Hi, I tested it last night on a few games, and it seems to work perfectly now! Thank you very much! :)
We are making some adjustments to the cocktail table, but as soon as it is ready again I'll share some photos!