The OpenGL GLSL IBMulator shader system implements the new (draft) libretro's Vulkan GLSL RetroArch shader system specification, with only minor differences and some extension.
IBMulator's shader system aims to be fully compatible with the Vulkan GLSL RetroArch shader system but keep in mind that:
- the libretro's specification is still a draft and evolving, so more recent versions of its shaders might fail to load in IBMulator.
- IBMulator is based on the public specification, so shaders that rely on specific RetroArch behaviours (and bugs) might not work as expected.
A shader is defined by a preset (.slangp
) and at least one source file
(.slang
).
To load a shader in IBMulator you need to specify the path of its .slangp preset
file in ibmulator.ini. You cannot load .slang source files directly.
A Vulkan GLSL RetroArch shader source is translated to OpenGL GLSL by IBMulator and can be used unmodified, assuming your video card supports the OpenGL version required by the shader. Most RetroArch shaders are created for GLSL version 4.50, so your drivers should support at least OpenGL 4.5 in case you want to use any of those. IBMulator's own shaders are for GLSL version 3.30 and the minimum required OpenGL version is 3.3.
The IBMulator shader format outlines a filter chain/graph, a series of shader passes which operate on previously generated data to produce a final result. Every individual pass can access information from all previous shader passes, even across frames.
- The filter chain specifies a number of shader passes to be executed one after the other.
- Each pass renders a full-screen quad to a texture of a certain resolution and format.
- The resolution can be dependent on external information.
- All filter chains begin at an input texture, which is generated by the emulated video card.
- All filter chains terminate by rendering to the "backbuffer".
The backbuffer is somewhat special since the resolution of it cannot be controlled by the shader. It can also not be fed back into the filter chain later because the frontend will render UI elements on top of the final pass output.
(Input) -> [ Shader Pass #0 ] -> (Backbuffer)
In this case there are no offscreen render targets necessary since the input is rendered directly to screen (backbuffer).
(Input) -> [ Shader Pass #0 ] -> (Framebuffer) -> [ Shader Pass #1 ] -> (Backbuffer)
Framebuffer here might have a different resolution than both Input and Backbuffer.
/------------------------------------------------\
/ v
(Input) -> [ Shader Pass #0 ] -> (Framebuffer #0) -> [ Shader Pass #1 ] -> (Backbuffer)
In this case, Shader pass #1 has two inputs, both the original input as well as the result of a pass in-between. All the inputs to a pass can have different resolutions.
Frame N: (Input N, Input N - 1, Input N - 2) -> [ Shader Pass #0 ] -> (Framebuffer N, Framebuffer N - 1, Input N - 3) -> [ Shader Pass #1 ] -> (Backbuffer)
Frame N - 1: (Input N - 1, Input N - 2, Input N - 3) -> [ Shader Pass #0 ] -> (Framebuffer N - 1, Framebuffer N - 2, Input N - 4) -> [ Shader Pass #1 ] -> (Backbuffer)
Frame N - 2: (Input N - 2, Input N - 3, Input N - 4) -> [ Shader Pass #0 ] -> (Framebuffer N - 2, Framebuffer N - 3, Input N - 5) -> [ Shader Pass #1 ] -> (Backbuffer)
For framebuffers the previous frame's framebuffer can be read. Just like IIR filters, the "response" of such a feedback in the filter graph gives essentially "infinite" history back in time.
Input textures can have arbitrary number of textures as history (just limited by memory). They cannot feedback since the filter chain cannot render into it, so it effectively is finite response (FIR).
For the very first frames, the content of Input frames with frame N < 0 is the same as the content of frame 0.
No texture in the filter chain is padded at any time. It is possible for resolutions in the filter chain to vary over time. In this scenarios, the textures and framebuffers are simply resized appropriately. Older frames still keep their old resolution in the brief moment that the resolution is changing.
There are three main types of inputs in this shader system.
- Texture samplers (sampler2D).
- Look-up textures for static input data.
- Uniform data describing dimensions of textures.
- Uniform ancillary data for render target dimensions, backbuffer target dimensions, frame count, etc.
- Uniform user-defined parameters.
- Uniform matrices for the vertex shader.
Deduction for what a sampler2D uniform wants to sample from is based on built-in identifiers which correspond to certain textures, eg.:
// Source here being defined as the texture from previous framebuffer pass or the input texture if this is the first pass in the chain.
uniform sampler2D Source;
The frontend will use reflection in order to deduce what each uniform or texture means. The main types of data passed to shaders are read-only and can be classified as:
uniform sampler2D
: This is used for input textures, framebuffer results and lookup-textures.uniform Block { };
: Uniform blocks can be used for builtins and user parameters that need to be passed to the shader.uniform (int|uint|float|vec4|mat4) NAME;
: Builtins and parameters can also be specified as uniforms at global scope.
Certain rules must be adhered to in order to make it easier for the frontend to dynamically set up bindings to resources.
- If version 4.20+ is used, layout(binding = #N) must be declared for all uniform blocks and sampler2Ds.
- If version 4.20+ is used, all resources must use different bindings.
- If a block is used in both vertex and fragment stages, their binding number must match.
- If a block is used in both vertex and fragment stages, members with the same name must have the same offset/binary interface.
- sampler2D cannot be used in vertex stage, although the size parameters of samplers can be used in vertex stage.
- Other resource types such as SSBOs, images, atomic counters, etc, etc, are not allowed.
- Every uniform, block member, and sampler2D must be meaningful to the frontend in some way.
Differences with the Vulkan GLSL RetroArch shader system:
- layout(push_constant) is ignored.
- There can be any number of uniform blocks and their names can be anything valid for GLSL.
The very first line of a .slang
file must contain a #version
statement.
#include
and #pragma include
are functionally the same.
#include
statements are translated to pragmas by IBMulator's preprocessor.
#include FILEPATH
statements are replaced by the text contained in FILEPATH
.
The include process does not consider any preprocessor #define
s or conditional
expressions.
The include path must always be relative, and it will be relative to the file path of the current file. Nested includes are allowed, but includes in a cycle are undefined as preprocessor guards are not considered.
E.g.:
#include "../common.inc"
This pragma controls which part of a .slang
file are visible to certain shader
stages.
Two variants of this pragma are supported:
#pragma stage vertex
#pragma stage fragment
If no #pragma stage
has been encountered yet, lines of code in a shader belong
to all shader stages.
If a #pragma stage
statement has been encountered, that stage is considered
active, and the following lines of shader code will only be used when building
source for that particular shader stage. A new #pragma stage
can override
which stage is active.
This pragma lets a shader set its identifier. This identifier can be used to
create aliases for other passes. This pragma has precedence over aliasN
defined in the preset file.
E.g.:
#pragma name HorizontalPass
Then to reference the framebuffer output of the current pass from a subsequent pass:
uniform sampler2D HorizontalPass;
This pragma controls the format of the framebuffer which this shader will render
to. The default render target format is R8G8B8A8_UNORM
. This pragma has
precedence over float_framebufferN
and srgb_framebufferN
in the preset file.
Available render target formats are listed below. Actual support depends on the OpenGL driver.
Rendering to uint/int formats requires an UINT/SINT fragment shader output target.
R8_UNORM
R8_UINT
R8_SINT
R8G8_UNORM
R8G8_UINT
R8G8_SINT
R8G8B8A8_UNORM
R8G8B8A8_UINT
R8G8B8A8_SINT
R8G8B8A8_SRGB
A2B10G10R10_UNORM_PACK32
A2B10G10R10_UINT_PACK32
R16_UINT
R16_SINT
R16_SFLOAT
R16G16_UINT
R16G16_SINT
R16G16_SFLOAT
R16G16B16A16_UINT
R16G16B16A16_SINT
R16G16B16A16_SFLOAT
R32_UINT
R32_SINT
R32_SFLOAT
R32G32_UINT
R32G32_SINT
R32G32_SFLOAT
R32G32B32A32_UINT
R32G32B32A32_SINT
R32G32B32A32_SFLOAT
E.g.:
#pragma format R16_SFLOAT
Shader parameters allow shaders to take user-defined inputs as uniform values.
Parameters can only be linked to uniforms of type float
.
The format is:
#pragma parameter IDENTIFIER "DESCRIPTION" INITIAL MINIMUM MAXIMUM [STEP]
IDENTIFIER
is the meaningful string which is also the name of the uniform.DESCRIPTION
is a string which is human readable representation ofIDENTIFIER
, used by the configuration tool in the GUI.INITIAL
,MINIMUM
andMAXIMUM
are floating point values for the uniform.STEP
(optional) represent the quantity by which the value is incremented or decremented using the configuration tool in the GUI.
E.g:
#pragma parameter DummyVariable "This is a dummy variable" 1.0 0.2 2.0 0.1
uniform Parameters {
float DummyVariable;
} params;
Parameters are defined in shader source files, but their IDENTIFIER
must is
unique to the shader chain. The definition of a parameter is set by the
first shader that declares IDENTIFIER
.
The value of a parameter is determined in this order:
- the
INITIAL
value. - the value set in the preset.
The shader spec specifies two vertex inputs and one fragment output. Varyings between vertex and fragment shaders are user-defined.
Two attributes are provided and must be present in a shader.
It is only the layout(location = #N)
which is actually significant.
The particular names of input and output variables are ignored, but should be
consistent for readability.
This attribute is a 2D position in the form vec4(x, y, 0.0, 1.0)
.
Shaders should not try to extract meaning from the x, y.
gl_Position
must be assigned, eg.:
gl_Position = MVP * Position;
The texture coordinate is semantically such that (0.0, 0.0) is top-left and
(1.0, 1.0) is bottom right.
If TexCoord is passed to a varying unmodified, the interpolated varying will be
uv = 0.5 / OutputSize
when rendering the upper left pixel as expected and
uv = 1.0 - 0.5 / OutputSize
when rendering the bottom-right pixel.
When using GLSL version 4.20 or later, vertex outputs and fragment inputs can link by location, otherwise they link by name.
E.g.:
#version 450
...
// Vertex
layout(location = 0) out vec4 varying;
// Fragment
layout(location = 0) in vec4 some_other_name;
Fragment shaders must have a single output to location = 0
. If no location is
specified it is assumed to be 0.
Multiple render targets are not allowed. The type of the output depends on the
render target format.
int/uint type must be used if UINT/SINT render target formats are used,
otherwise float type.
The input of textures get their meaning from their name.
Original
: This accesses the input of the filter chain (the video card's output), accessible from any pass.Source
: This accesses the output from the previous shader pass, orOriginal
if accessed in the first pass of the filter chain.OriginalHistory#
: This accessesOriginal
# frames back in time. There is no limit on #, except larger numbers will consume more VRAM.OriginalHistory0
is an alias forOriginal
,OriginalHistory1
is the previous frame and so on.PassOutput#
: This accesses the output from pass # in this frame.PassOutput#
must be causal, as it is an error to access PassOutputN in pass M if N >= M.PassOutput#
will typically be aliased to a more readable value.PassFeedback#
: This accessesPassOutput#
from the previous frame. Any pass can read the feedback of any feedback, since it is causal.PassFeedback#
will typically be aliased to a more readable value.
Differences with the Vulkan GLSL RetroArch shader system:
User#
: look-up textures access by number#
is not supported.
If a uniform is called ???Size#
where ???#
is the name of a texture
variable, that member must be a vec4
, which will receive these values:
x
: Horizontal size of texturey
: Vertical size of texturez
: 1.0 / (Horizontal size of texture)w
: 1.0 / (Vertical size of texture)
It is valid to use a size variable without declaring the texture itself. It is valid for a variable to be present in multiple uniform blocks at the same time.
E.g.:
uniform Block {
vec4 OriginalSize;
vec4 OriginalHistorySize1;
vec4 MyTextureSize;
} globals;
uniform vec4 SourceSize;
Other than sampler related uniforms, there are other special uniforms available. These builtin variables may be declared as global uniforms or part of a uniform block.
MVP
: mat4 Model View Projection matrix.OutputSize
: a vec4(x, y, 1.0 / x, 1.0 / y) variable describing the render target size (x, y) for this pass.FinalViewportSize
: a vec4(x, y, 1.0 / x, 1.0 / y) variable describing the render target size for the final pass. Accessible from any pass.FrameCount
: a uint variable taking a value which increases by one every frame. This value could be pre-wrapped by modulo if specified in the preset.
IBMulator's extensions to the Vulkan GLSL RetroArch shader system:
ibmu_Projection
: mat4 Projection matrix.ibmu_ModelView
: mat4 Model View matrix.ibmu_Brightness
,ibmu_Contrast
,ibmu_Saturation
: float values for the brightness, contrast, and saturation levels as set by the user via the CRT controls.ibmu_Ambient
: float, the ambient light intensity.ibmu_Monochrome
: int with boolean values, 1 if the installed system monitor is monochromatic, 0 if color.ibmu_PowerOn
: int with boolean values, 1 when the machine is on, 0 when off.ibmu_PassNumber
: uint taking the pass number of the shader.
Aliases can give meaning to arbitrary names in a shader file.
If a shader pass has a #pragma name NAME
or aliasN=NAME
associated with it,
meaning is given to the shader:
NAME
, is a sampler2D.NAMESize
is a vec4 size uniform associated withNAME
.NAMEFeedback
is a sampler2D for the previous frame.NAMEFeedbackSize
is a vec4 size uniform associated withNAMEFeedback
.
User textures must be accessed by their alias specified in the preset. If a
texture has an alias MYTEXTURE
then:
MYTEXTURE
is a sampler2D.MYTEXTURESize
is a vec4 size uniform associated withMYTEXTURE
.
#version 450
layout(binding = 0, std140) uniform UBO
{
mat4 MVP;
vec4 SourceSize; // Not used here (ignored)
float ColorMod;
} globals;
#pragma name StockShader
#pragma format R8G8B8A8_UNORM
#pragma parameter ColorMod "Color intensity" 1.0 0.1 2.0 0.1
#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 = globals.MVP * Position;
vTexCoord = TexCoord;
}
#pragma stage fragment
layout(location = 0) in vec2 vTexCoord;
layout(location = 0) out vec4 FragColor;
layout(binding = 1) uniform sampler2D Source;
void main()
{
FragColor = texture(Source, vTexCoord) * globals.ColorMod;
}
The configuration of the OpenGL's sampler object associated with a sampler2D
uniform is specified by the preset format.
The value of the ibmu_samplers_mode
setting determines the way samplers for
builtin textures are created. User textures have their own samplers.
Either:
- every builtin texture has its own sampler configuration that is used by all the passes that use that texture.
- or every pass has a general input sampler that will be used for sampling
every builtin texture (
Original
,Source
, ...) it uses.
The RetroArch shader system adheres to the first interpretation (apparently).
Sampler objects remain constant throughout the frame, there is no way to select samplers on a frame-by-frame basis.
The input to the filter chain (the Original
texture) will not be of an sRGB
format. If needed, it's possible to have a first pass which linearizes the input
to a proper sRGB render target. In this way, custom gammas can be used as well.
Similarly, the final pass will not be an sRGB backbuffer.
Every shader will have the following #define
statements added right after the
#version
statement.
IBMU_PASS_NUMBER
: receives the number of the current pass.
Custom additional substitutions can be specified in the preset file.
Shader chains are defined in text files having the .slangp
extension.
The purpose of preset files is to combine several shader programs into a multi-pass shader, defining all the necessary configuration parameters like scaling, filtering, etc, as well as user textures.
Preset files use a syntax similar to .ini files:
- values are set with
key = value
pairs. - a global unnamed block is used so
[section]
blocks are ignored. - single line comments starts with
;
or#
or/
. If a comment is at the end of a key-value pair, the comment character must be separated from the value by at least a blank space. - comment blocks are
/* ... */
.
Values can be surrounded by double quotation marks ("
). In this case the "
characters are ignored.
-
shaders
(int): defines the number of passes, ie. how many slang shaders will be loaded. This value must be at least one.
Required -
ibmu_samplers_mode
(string): sets the way sampler objects for builtin textures are created (user textures have their own samplers).- "texture": sampler objects are created per texture, ie. textures are sampled using the sampler object stored in the textures.
- "pass": sampler objects are created per pass, ie. every pass uses its own sampler object for every builtin texture it uses.
Default: "texture"
Sampler objects are configured with
filter_linearN
,wrap_modeN
, andmipmap_inputN
. -
ibmu_input_size
(string): sets the size of the input textureOriginal
. Can be set to one of these values:- "crtc": the size is equivalent to the resolution generated by the video card's CRTC clocking.
- "video_mode": the size is set by the current video mode, and is determined by the CRTC resolution, the Clocking Mode Register, and the Maximum Scan Line Register.
Default: "video_mode"
E.g.: the famous Mode 13h video mode is 320x200 pixels. It is generated using a screen resolution of 640x400 pixels. Using "crtc" the input texture will have a size of 640x400, whereas with "video_mode" it will have a size of 320x200.
-
ibmu_output_size
(string): forces the output to a particular resolution. This acts as an override toshader_output_size
in ibmulator.ini. Can be set to one of these values:- "native": use the native resolution of the monitor.
- "WxH": a specific size in pixels, maintaining the viewport's aspect ratio.
- "max_WxH": a maximum size in pixels, maintaining the viewport's aspect ratio.
Default: not set
-
shaderN
(string): defines the path of the shader for pass N, where N goes from 0 toshaders
- 1. The path is relative to the directory the preset was loaded from.
Required -
aliasN
(string): defines a name alias for pass N. Aliases are used to give meaningful names to samplers of pass outputs.
Default: "" -
filter_linearN
(boolean): enables linear filtering for the output texture of pass N-1 or input sampler object for pass N (depending on the configuration).
Default: true -
mipmap_inputN
(boolean): enables mip-mapping for output texture of pass N-1 or the inputs referenced by pass N (depending on the configuration).
Default: false -
wrap_modeN
(string): defines the repeating mode of the output texture of pass N-1 or the input sampler object of pass N (depending on the configuration). Can be set to one of these values:- "repeat": equivalent to GL_REPEAT.
- "clamp_to_border": equivalent to GL_CLAMP_TO_BORDER.
- "clamp_to_edge": equivalent to GL_CLAMP_TO_EDGE.
- "mirrored_repeat": equivalent to GL_MIRRORED_REPEAT.
- "mirror_clamp_to_edge": equivalent to GL_MIRROR_CLAMP_TO_EDGE.
Default: "clamp_to_border", with border color (0,0,0,0).
-
float_framebufferN
(boolean): defines if shader N should render to a 32-bit floating point buffer. This only takes effect if shader N is actually rendered to an FBO. If the shader file has a valid#pragma format
directive, this value is ignored.
Default: false -
srgb_framebufferN
(boolean): defines if shader N should render to an sRGB buffer. This only takes effect if shader N is actually rendered to an FBO. If the shader file has a valid#pragma format
directive, this value is ignored.float_framebufferN
takes precedence.
Default: false -
frame_count_modN
(uint): defines which modulo to apply toFrameCount
for pass N.FrameCount
will take the valueFrameCount % frame_count_modN
.
Default: 0 (disabled) -
scale_typeN
,scale_type_xN
,scale_type_yN
(string): can be set to one of these values:- "original": output size of shader N is relative to the size of the
Original
texture. - "source": output size of shader N is relative to the size of the
Source
texture. - "viewport": output size of shader N is relative to the value of
FinalViewportSize
. This value will change when the user resizes their window. - "absolute": output size is statically defined to a certain size.
Default: "viewport" or "source"
If no scale_type is set for the very last shader, the default is "viewport", otherwise "source". If there is only one shader, it is considered to be the very last shader. If the last shader has "viewport" with both scale values set to 1.0 and no feedback is needed then rendering is direct to backbuffer. If any other scaling combination is defined, it has to go through a FBO, and subsequently rendered to screen. The filtering option used when stretching to the backbuffer is set in ibmulator.ini.
- "original": output size of shader N is relative to the size of the
-
scaleN
,scale_xN
,scale_yN
(float/int): these values control the scaling params fromscale_typeN
. The values may be either floating or integer depending on the type.scaleN
controls both scaling type in horizontal and vertical directions.
Default: 1.0If
scaleN
is defined,scale_xN
andscale_yN
have no effect.scale_xN
andscale_yN
control scaling properties for the directions separately.Should
scale_type_xN
andscale_type_yN
be set to different values, the use ofscaleN
is undefined (i.e. if X-type is "absolute" (takes int), and Y-type is "source" (takes float).) -
ibmu_blending_outputN
(boolean): enables blending during rendering of pass N.
Default: false -
textures
(multiple strings): defines one or more user textures names. Several names are delimited with;
like"TEXNAME1;TEXNAME2;..."
E.g.:
textures = "foo;bar"
These texture names serves as the names for GLSL sampler uniforms.
E.g.:
uniform sampler2D foo; uniform sampler2D bar;
-
TEXNAME
(string): sets the path of the textureTEXNAME
defined intextures
.E.g.:
foo = image0.png bar = ../resources/image1.png
Paths are relative to the directory the preset was loaded from.
The only supported file format is PNG.
-
TEXNAME_linear
orTEXNAME_filter_linear
(boolean): enables linear filtering for texture namedTEXNAME
.
Default: true -
TEXNAME_wrap_mode
orTEXNAME_repeat_mode
(string): defines the repeating mode for textureTEXNAME
. Can be set to one of these values:- "repeat": equivalent to GL_REPEAT.
- "clamp_to_border": equivalent to GL_CLAMP_TO_BORDER.
- "clamp_to_edge": equivalent to GL_CLAMP_TO_EDGE.
- "mirrored_repeat": equivalent to GL_MIRRORED_REPEAT.
- "mirror_clamp_to_edge": equivalent to GL_MIRROR_CLAMP_TO_EDGE.
Default: "clamp_to_border", with border color (0,0,0,0).
-
TEXNAME_mipmap
(boolean): enables mip-mapping for textureTEXNAME
.
Default: false -
ibmu_defines
(multiple strings): adds one or more#define
preprocessor statements to all the shaders in the chain immediatly after the#version
statement. Several values are delimited with;
like"DEFINE1;DEFINE2;..."
E.g.:
ibmu_defines = "MYVALUE;MYCOLOR"
-
DEFINE
(string): sets the value forDEFINE
declared inibmu_defines
.E.g.:
MYVALUE = "0.5" MYCOLOR = "vec3(1.0,MYVALUE,0.0)"
Values are used as they are, without any interpretation. Thus the
#define
s for the previous examples are:#define MYVALUE 0.5 #define MYCOLOR vec3(1.0,MYVALUE,0.0)
A shader can be integrated in the IBMulator's realistic GUI mode using the following preset settings:
-
ibmu_rendering_size
(string): tells the renderer what part of the system the shader is supposed to render. Can be one of:- "vga": only the graphics card's output
- "crt": the VGA output and the CRT glass
- "monitor": the whole monitor, including the frame/bezels, the CRT glass, and the VGA.
Default: "vga"
-
ibmu_monitor_width
(int): total monitor width in pixels. -
ibmu_monitor_height
(int): total monitor height in pixels. -
ibmu_crt_width
(int): the CRT width in pixels. -
ibmu_crt_height
(int): the CRT height in pixels. -
ibmu_monitor_bezelw
(int): the left/right bezel width in pixels. -
ibmu_monitor_bezelh
(int): the top bezel height in pixels. -
ibmu_vga_scale
(float): scale factor of the VGA relative to the CRT glass, from 0.0 to 1.0.
Pixel values generally refer to a texture.
Preset files can be referenced by other preset files via the #reference
statement.
The referenced preset will be used as a base and the values of its keys will be overridden by the same keys in the current preset.
PRESET
path must always be relative, and it will be relative to the path of
the current file. Nested references are allowed, but references in a cycle will
result in an error.
E.g.:
#reference 'base_preset.slangp'
MyValue = 2.0
where base_preset.slangp
contains:
shaders = 1
shader0 = myshader.slang
MyValue = 1.0
will result in:
shaders = 1
shader0 = myshader.slang
MyValue = 2.0