Shaders¶
This guide covers the PS4 shader binary format, stage register setup, fetch shader generation, and shader binding to the pipeline.
Shader Stages¶
The PS4 GPU (GCN architecture) supports the following shader stages:
| Stage | Enum | Register Struct | Set Function |
|---|---|---|---|
| Compute | GNM_STAGE_CS |
GnmCsStageRegisters |
sceGnmSetCsShader |
| Pixel | GNM_STAGE_PS |
GnmPsStageRegisters |
sceGnmSetPsShader |
| Vertex | GNM_STAGE_VS |
GnmVsStageRegisters |
sceGnmSetVsShader |
| Geometry | GNM_STAGE_GS |
GnmGsStageRegisters |
sceGnmSetGsShader |
| Export | GNM_STAGE_ES |
GnmEsStageRegisters |
sceGnmSetEsShader |
| Hull | GNM_STAGE_HS |
GnmHsStageRegisters |
sceGnmSetHsShader |
| Local | GNM_STAGE_LS |
GnmLsStageRegisters |
sceGnmSetLsShader |
Stage Registers¶
Each shader stage has a register struct that the GPU reads to find the shader program and its configuration. The most important fields are the program address (split into lo/hi 32-bit halves) and the resource registers.
Setting the Shader Address¶
GnmVsStageRegisters vsRegs;
sceGnmVsRegsSetAddress(&vsRegs, shaderCodePtr);
GnmPsStageRegisters psRegs;
sceGnmPsRegsSetAddress(&psRegs, shaderCodePtr);
GnmCsStageRegisters csRegs;
sceGnmCsRegsSetAddress(&csRegs, shaderCodePtr);
The address is stored as address >> 8 (256-byte alignment, matching
GNM_ALIGNMENT_SHADER_BYTES).
VS Stage Registers (0x1C bytes)¶
| Field | Description |
|---|---|
spishaderpgmlovs |
Program address low (>> 8) |
spishaderpgmhivs |
Program address high (>> 40) |
spishaderpgmrsrc1vs |
Resource config 1 |
spishaderpgmrsrc2vs |
Resource config 2 |
spivsoutconfig |
Output configuration |
spishaderposformat |
Position format |
paclvsoutcntl |
Output control |
PS Stage Registers (0x30 bytes)¶
| Field | Description |
|---|---|
spishaderpgmlops |
Program address low (>> 8) |
spishaderpgmhips |
Program address high (>> 40) |
spishaderpgmrsrc1ps |
Resource config 1 |
spishaderpgmrsrc2ps |
Resource config 2 |
spishaderzformat |
Z format |
spishadercolformat |
Color export format (GnmPsExportFormat) |
spipsinputena |
PS input enable |
spipsinputaddr |
PS input address |
spipsincontrol |
PS input control |
spibaryccntl |
Barycentric control |
dbshadercontrol |
DB shader control |
cbshadermask |
Color buffer shader mask |
Shader Binary Format¶
PS4 shaders are stored in a binary container format with a file header, common data, stage-specific data, input usage slots, and the shader code.
File Header (0x10 bytes)¶
typedef struct {
uint32_t magic; // GNM_SHADER_FILE_HEADER_ID (0x72646853 = "Shdr")
uint16_t vermajor;
uint16_t verminor;
uint8_t type; // GnmShaderType
uint8_t headersizedwords;
uint8_t auxdata;
uint8_t targetgpumodes; // GnmTargetGpuMode
uint32_t _unused;
} GnmShaderFileHeader;
Common Data (0x8 bytes)¶
typedef struct {
uint32_t shadersize : 23;
uint32_t isusingsrt : 1;
uint32_t numinputusageslots : 8;
uint16_t embeddedconstantbufferdqwords;
uint16_t scratchsizeperthreaddwords;
} GnmShaderCommonData;
VS Shader (0x28 bytes + tables)¶
typedef struct {
GnmShaderCommonData common;
GnmVsStageRegisters registers;
uint8_t numinputsemantics;
uint8_t numexportsemantics;
uint8_t gsmodeornuminputsemanticscs;
uint8_t fetchcontrol;
} GnmVsShader;
After the GnmVsShader struct, the binary contains:
GnmInputUsageSlot[numinputusageslots]— resource usage tableGnmVertexInputSemantic[numinputsemantics]— vertex input semanticsGnmVertexExportSemantic[numexportsemantics]— vertex export semantics- Shader code (at offset
registers.spishaderpgmlovs)
PS Shader (0x3C bytes + tables)¶
typedef struct {
GnmShaderCommonData common;
GnmPsStageRegisters registers;
uint8_t numinputsemantics;
uint8_t _unused[3];
} GnmPsShader;
After the struct: input usage slots, then pixel input semantics, then code.
Shader Binary Info (0x1C bytes)¶
Embedded at the end of the shader code:
typedef struct {
uint8_t signature[7]; // "OrbShdr"
uint8_t version;
uint32_t ispsslcg : 1;
uint32_t issourcecached : 1;
uint32_t type : 4; // GnmShaderBinaryType
uint32_t sourcetype : 2;
uint32_t length : 24; // Shader code length
uint8_t chunkusagebaseoffsetdwords;
uint8_t numinputusageslots;
// ...
uint32_t shaderhash0;
uint32_t shaderhash1;
uint32_t crc32;
} GnmShaderBinaryInfo;
Extracting Shader Metadata¶
Use the helper API to parse a shader binary:
#include <gnm_helpers.h>
GnmShaderMetadata meta;
GnmError err = sceGnmShaderBinaryGetMetadata(
shaderData, shaderSize, &meta
);
GnmShaderMetadata contains:
| Field | Description |
|---|---|
type |
GnmShaderType (VS, PS, CS, etc.) |
targetgpumodes |
GnmTargetGpuMode |
versionmajor / versionminor |
Shader version |
fileheader |
Pointer to GnmShaderFileHeader |
common |
Pointer to GnmShaderCommonData |
stage |
Pointer to stage-specific data |
stagesize |
Size of stage-specific data |
inputusageslots |
Pointer to GnmInputUsageSlot array |
numinputusageslots |
Count of input usage slots |
numinputsemantics |
Count of input semantics |
numexportsemantics |
Count of export semantics |
shadercode |
Pointer to shader code |
shadercodesize |
Size of shader code |
Inline Accessors¶
const GnmShaderCommonData* common = sceGnmShfCommonData(fileHeader);
const GnmInputUsageSlot* slots = sceGnmVsShaderInputUsageSlotTable(vsShader);
const GnmVertexInputSemantic* inputs = sceGnmVsShaderInputSemanticTable(vsShader);
const GnmVertexExportSemantic* exports = sceGnmVsShaderExportSemanticTable(vsShader);
uint32_t size = sceGnmVsShaderCalcSize(vsShader);
const void* code = sceGnmVsShaderCodePtr(vsShader);
Input Usage Slots¶
GnmInputUsageSlot (4 bytes) describes what resources a shader expects in its
user data registers:
typedef struct {
uint8_t usagetype; // GnmShaderInputUsageType
uint8_t apislot;
uint8_t startregister;
union {
struct {
uint8_t registercount : 1;
uint8_t resourcetype : 1;
uint8_t _unused : 2;
uint8_t chunkmask : 4;
};
uint8_t srtdwordsminusone;
};
} GnmInputUsageSlot;
Usage Types¶
| Type | Description | Size (dwords) |
|---|---|---|
GNM_SHINPUTUSAGE_IMM_RESOURCE |
Immediate resource (T#) | 0 |
GNM_SHINPUTUSAGE_IMM_SAMPLER |
Immediate sampler (S#) | 4 |
GNM_SHINPUTUSAGE_IMM_CONSTBUFFER |
Immediate constant buffer (V#) | 4 |
GNM_SHINPUTUSAGE_IMM_VERTEXBUFFER |
Immediate vertex buffer (V#) | 4 |
GNM_SHINPUTUSAGE_IMM_RWRESOURCE |
Immediate RW resource | 0 |
GNM_SHINPUTUSAGE_IMM_ALUFLOATCONST |
Immediate float constant | 1 |
GNM_SHINPUTUSAGE_IMM_ALUBOOL32CONST |
Immediate bool constant | 1 |
GNM_SHINPUTUSAGE_SUBPTR_FETCHSHADER |
Fetch shader sub-pointer | 2 |
GNM_SHINPUTUSAGE_PTR_RESOURCETABLE |
Resource table pointer | 2 |
GNM_SHINPUTUSAGE_PTR_SAMPLERTABLE |
Sampler table pointer | 2 |
GNM_SHINPUTUSAGE_PTR_CONSTBUFFERTABLE |
Constant buffer table pointer | 2 |
GNM_SHINPUTUSAGE_PTR_VERTEXBUFFERTABLE |
Vertex buffer table pointer | 2 |
Use sceGnmShaderInputUsageTypeSize() to get the size in dwords for a usage type.
Fetch Shaders¶
Fetch shaders are small GCN programs that load vertex data from vertex buffers. opengnm can generate fetch shaders at runtime using the GCN assembler.
Creating a Fetch Shader¶
#include <gnm_shader.h>
GnmFetchShaderCreateInfo ci = {
.regs = &vsRegs,
.flags = GNM_FETCH_FLAG_NONE,
.inputusages = inputUsageSlots,
.numinputusages = numSlots,
.vtxinputs = vtxInputSemantics,
.numvtxinputs = numInputs,
.remaptable = remapTable,
.remaptablecount = remapCount,
.instancedata = instanceModes,
.numinstancedata = numInstanceModes,
.vertexbaseusgpr = 0,
.instancebaseusgpr = 0,
};
// Calculate required size
uint32_t fetchSize;
sceGnmFetchShaderCalcSize(&fetchSize, &ci);
// Allocate and generate
void* fetchShader = aligned_alloc(256, fetchSize);
GnmFetchShaderResults results;
sceGnmCreateFetchShader(fetchShader, fetchSize, &ci, &results);
// Apply the fetch shader modifier to VS registers
sceGnmVsRegsSetFetchShaderModifier(&vsRegs, &results);
Fetch Shader Results¶
typedef struct {
uint32_t sgprs; // Number of SGPRs used
uint32_t vgprcompcnt; // VGPR compute count
} GnmFetchShaderResults;
Binding Shaders¶
In the draw command buffer¶
// Set VS shader
sceGnmDrawCmdSetVsShader(&cmd, &vsRegs, 0);
// Set PS shader
sceGnmDrawCmdSetPsShader(&cmd, &psRegs);
// Set embedded shaders (built-in firmware shaders)
sceGnmDrawCmdSetEmbeddedVsShader(&cmd, GNM_EMBEDDED_VSH_FULLSCREEN, 0);
sceGnmDrawCmdSetEmbeddedPsShader(&cmd, GNM_EMBEDDED_PSH_DUMMY);
// Set CS shader (for compute dispatch)
sceGnmDrawCmdSetCsShader(&cmd, &csRegs);
Via the driver runtime¶
sceGnmSetVsShader(cmdbuf, size, vsRegs, 0);
sceGnmSetPsShader(cmdbuf, size, psRegs);
sceGnmSetCsShader(cmdbuf, size, csRegs);
Updating shaders¶
For partial shader updates without re-sending the full shader:
PS Input Usage¶
Connect VS exports to PS inputs:
Pixel Input Semantic (2 bytes)¶
typedef union {
struct {
uint16_t semantic : 8;
uint16_t defaultvalue : 2; // GnmPixelDefaultValue
uint16_t isflatshaded : 1;
uint16_t islinear : 1;
uint16_t iscustom : 1;
uint16_t _unused : 3;
};
// NEO mode fields...
} GnmPixelInputSemantic;
Default Values¶
| Value | Description |
|---|---|
GNM_PX_DEFVAL_NONE |
No default |
GNM_PX_DEFVAL_0_0_0_1 |
{0, 0, 0, 1} |
GNM_PX_DEFVAL_1_1_1_0 |
{1, 1, 1, 0} |
GNM_PX_DEFVAL_1_1_1_1 |
{1, 1, 1, 1} |
Embedded Shaders¶
The PS4 firmware includes built-in shaders for common operations. opengnm
exposes them through the sceGnmDrawCmdSetEmbeddedVsShader /
sceGnmDrawCmdSetEmbeddedPsShader draw-command-buffer entry points, which
flow through the sceGnmDriverSetEmbedded* wrappers in the Orbis backend.
Embedded VS Shaders¶
| Enum | Description |
|---|---|
GNM_EMBEDDED_VSH_FULLSCREEN |
Full-screen triangle |
Embedded PS Shaders¶
| Enum | Description |
|---|---|
GNM_EMBEDDED_PSH_DUMMY |
Dummy pixel shader |
GNM_EMBEDDED_PSH_DUMMY_RG32 |
Dummy pixel shader (RG32 format) |
Backend Behavior¶
The sceGnmDriverSetEmbedded* wrappers use runtime HLE detection to select
between two emission paths:
On real PS4 hardware (libSceGnmDriver module loaded), the wrappers emit
the PM4 register-write sequence locally using embedded shader register blobs
and pad the reserved dword space with PKT3_NOP. This avoids the FW 9.00
crash in firmware shader-set helpers (sceGnmSetVsShader crashes inside the
GPU work pump context) while producing identical GPU state.
Under HLE emulators such as shadPS4 (where there is no
libSceGnmDriver module), the wrappers forward to the HLE-exposed
sceGnmSetEmbeddedVsShader / sceGnmSetEmbeddedPsShader entry points. The
emulator then writes its own built-in dummy shader code and registers.
The HLE forwarding path is necessary because the local register blobs
reference program addresses that only exist on real hardware; under shadPS4,
SearchBinaryInfo would fail with "Shader binary info not found" as soon as
a sample used an embedded PS shader. See
Backends — HLE Emulator Support
for the full rationale.