# How SharpEmu Handles Shader Submission: PS4 GCN to Vulkan SPIR-V Translation

> Discover how SharpEmu handles shader submission: PS4 GCN to Vulkan SPIR-V translation. Learn about its three-stage pipeline, aggressive caching, and Vulkan background thread execution.

- Repository: [Berk/sharpemu](https://github.com/par274/sharpemu)
- Tags: deep-dive
- Published: 2026-07-13

---

**SharpEmu processes shader submission through a three-stage pipeline: parsing PlayStation 4 AGC command buffers, translating native GCN shader binaries to SPIR-V bytecode with aggressive caching, and executing the work on a dedicated Vulkan background thread.**

The SharpEmu emulator (par274/sharpemu) implements a sophisticated graphics pipeline that bridges the PlayStation 4's proprietary AGC (AMD GPU Services) driver layer to modern Vulkan APIs. Understanding how this open-source emulator handles shader submission reveals the complexity of translating proprietary GPU command streams into cross-platform GPU instructions.

## The Three-Layer Shader Submission Pipeline

SharpEmu organizes its graphics pipeline into distinct architectural layers that transform native PS4 GPU commands into executable Vulkan work items.

### Layer 1: AGC Driver Command Buffer Parsing

The entry point for all GPU work occurs in [`SharpEmu.Libs.Agc.AgcExports.cs`](https://github.com/par274/sharpemu/blob/main/SharpEmu.Libs.Agc.AgcExports.cs). When an emulated game issues a draw or compute dispatch, it calls `sceAgcDriverSubmitDcb` (Driver Command Buffer) exports. The `AgcExports.DriverSubmitDcb` method reads the raw command buffer and invokes `ParseSubmittedDcb` to extract:

- **Shader addresses** (`pixelShaderAddress`, `vertexShaderAddress`)
- **Texture descriptors** and global memory buffers
- **Render state** (primitive type, vertex attribute count, viewport settings)

This HLE (High-Level Emulation) parsing decodes the PS4's proprietary command buffer format without requiring low-level hardware access.

### Layer 2: Shader Translation and SPIR-V Generation

Once `ParseSubmittedDcb` identifies shader addresses, the system initiates binary translation. The `Gen5ShaderTranslator` class ([`SharpEmu.Libs.Agc.Gen5ShaderTranslator.cs`](https://github.com/par274/sharpemu/blob/main/SharpEmu.Libs.Agc.Gen5ShaderTranslator.cs)) handles the heavy lifting:

1. `Gen5ShaderTranslator.TryDecodeProgram` reads the raw GCN (Graphics Core Next) binary from guest memory
2. It builds an intermediate representation of the shader logic
3. `Gen5SpirvTranslator.TryCompile` (in [`SharpEmu.Libs.Agc.Gen5SpirvTranslator.cs`](https://github.com/par274/sharpemu/blob/main/SharpEmu.Libs.Agc.Gen5SpirvTranslator.cs)) emits standard SPIR-V bytecode compatible with Vulkan drivers

To avoid recompiling identical shaders, SharpEmu maintains several cache dictionaries:
- `_graphicsSpirvCache` – stores translated vertex and pixel shaders
- `_computeSpirvCache` – stores compute shader translations
- `_shaderDigests` – tracks shader hashes for quick lookup

### Layer 3: Vulkan Queue Submission

The final layer resides in [`SharpEmu.Libs.VideoOut.VulkanVideoPresenter.cs`](https://github.com/par274/sharpemu/blob/main/SharpEmu.Libs.VideoOut.VulkanVideoPresenter.cs). This component receives the SPIR-V bytecode and resource bindings, then manages the actual GPU submission through three primary methods:

- **`SubmitTranslatedDraw`** – for rasterized graphics (vertex + pixel shader pairs)
- **`SubmitComputeDispatch`** – for compute workloads
- **`TrySubmitGuestImage`** – for raw image presentation (used by `sceVideoOutSubmitFlip`)

## Detailed Execution Flow from PS4 Binary to GPU

The complete shader submission lifecycle follows a precise path through SharpEmu's architecture:

1. **Game submission** – The emulated title calls `sceAgcDriverSubmitDcb` with a command buffer containing draw or compute instructions.

2. **Command parsing** – `AgcExports.ParseSubmittedDcb` decodes the opcode to determine if this is a raster draw or compute dispatch, extracting shader addresses and texture lists.

3. **Shader lookup** – The system checks `_graphicsSpirvCache` or `_computeSpirvCache` for existing translations. Cache misses trigger the `Gen5ShaderTranslator` → `Gen5SpirvTranslator` pipeline.

4. **Work item creation** – For raster draws, the system creates a `VulkanTranslatedGuestDraw` object containing the SPIR-V shaders, texture handles, and viewport dimensions. Compute dispatches use `VulkanComputeGuestDispatch`.

5. **Queue insertion** – The work item is enqueued in `_enqueuedGuestWork` under the protection of a global `_gate` lock to ensure thread safety.

6. **Background execution** – A dedicated background thread (`Run` method) continuously drains the queue, constructs Vulkan `PipelineShaderStageCreateInfo` structures, binds descriptor sets for textures and uniform buffers, and issues the final `vkQueueSubmit` call.

## Code Examples

### Submitting a Raster Draw (Vertex + Pixel Shaders)

When handling standard 3D rendering, SharpEmu submits both vertex and pixel shader stages:

```csharp
// SPIR-V bytecode obtained from Gen5SpirvTranslator
byte[] vertexSpirv = GetCachedOrTranslateShader(vertexAddr);
byte[] pixelSpirv = GetCachedOrTranslateShader(pixelAddr);

// Define texture inputs
var textures = new List<VulkanGuestDrawTexture> {
    new VulkanGuestDrawTexture(
        address: 0x1234_0000, 
        width: 256, 
        height: 256, 
        format: 12, 
        isStorage: false)
};

// Submit to Vulkan layer
VulkanVideoPresenter.SubmitTranslatedDraw(
    pixelSpirv: pixelSpirv,
    textures: textures,
    globalMemoryBuffers: new List<VulkanGuestMemoryBuffer>(),
    width: 1920,
    height: 1080,
    attributeCount: 3,
    vertexSpirv: vertexSpirv,
    vertexCount: 3,
    instanceCount: 1,
    primitiveType: 4);  // 4 == TRIANGLE_LIST

```

### Submitting a Compute Shader Dispatch

Compute workloads follow a similar path but use different submission parameters:

```csharp
ulong shaderAddr = 0xABCD_0000;  // Original GCN shader address
byte[] computeSpirv = TranslateComputeShader(shaderAddr);

var textures = new List<VulkanGuestDrawTexture> {
    new VulkanGuestDrawTexture(
        address: 0x2000_0000, 
        width: 64, 
        height: 64,
        format: 12, 
        isStorage: true)  // Storage image for compute write
};

VulkanVideoPresenter.SubmitComputeDispatch(
    shaderAddress: shaderAddr,
    computeSpirv: computeSpirv,
    textures: textures,
    globalMemoryBuffers: new List<VulkanGuestMemoryBuffer>(),
    groupCountX: 8,
    groupCountY: 8,
    groupCountZ: 1);

```

### Submitting a Raw Image Flip

For presenting rendered frames to the display output:

```csharp
ulong guestImage = 0x3000_0000;
bool submitted = VulkanVideoPresenter.TrySubmitGuestImage(
    address: guestImage,
    width: 1280,
    height: 720,
    pitchInPixel: 0);

if (!submitted) {
    Logger.Warning("[LOADER][WARN] Failed to submit guest image flip");
}

```

## Shader Caching Strategy

SharpEmu implements aggressive caching to minimize translation overhead. The `_graphicsSpirvCache` and `_computeSpirvCache` dictionaries map original PS4 shader addresses to compiled SPIR-V byte arrays. The `_shaderDigests` collection maintains hashes of shader binaries to detect identical shaders across different command buffers.

This caching proves critical for performance, as GCN-to-SPIR-V translation involves expensive analysis and optimization passes. By storing the final Vulkan-ready bytecode, subsequent draws using identical shaders execute with minimal overhead.

## Threading and Queue Management

The `VulkanVideoPresenter` uses a producer-consumer pattern to decouple emulation timing from GPU execution. The main emulation thread (producer) parses commands and generates `VulkanTranslatedGuestDraw` or `VulkanComputeGuestDispatch` objects, pushing them to `_enqueuedGuestWork`.

A dedicated background thread (consumer) runs the `Run` method, which:
- Locks the `_gate` mutex
- Dequeues pending work items
- Builds Vulkan command buffers with `PipelineShaderStageCreateInfo` for each shader stage
- Binds descriptor sets referencing the original PS4 texture addresses
- Calls `vkQueueSubmit` to execute on the physical GPU

This architecture prevents emulation stalls during GPU pipeline flushes or shader compilation, maintaining smooth frame rates even when processing complex shader graphs.

## Summary

- **Three-stage pipeline** – SharpEmu parses AGC command buffers in [`AgcExports.cs`](https://github.com/par274/sharpemu/blob/main/AgcExports.cs), translates GCN binaries to SPIR-V via `Gen5ShaderTranslator` and `Gen5SpirvTranslator`, and submits to Vulkan through `VulkanVideoPresenter`.
- **Aggressive caching** – The `_graphicsSpirvCache` and `_computeSpirvCache` dictionaries eliminate redundant shader recompilation.
- **Thread-safe submission** – Background thread processing via `_enqueuedGuestWork` ensures emulation and GPU execution remain decoupled.
- **Three submission types** – `SubmitTranslatedDraw` for raster graphics, `SubmitComputeDispatch` for compute workloads, and `TrySubmitGuestImage` for display presentation.

## Frequently Asked Questions

### How does SharpEmu translate PS4 shaders to run on PC GPUs?

SharpEmu translates shaders using the `Gen5ShaderTranslator` class to decode the raw GCN binary into an intermediate representation, then `Gen5SpirvTranslator` compiles this to standard SPIR-V bytecode that any Vulkan-compatible GPU can execute. This translation happens transparently when `ParseSubmittedDcb` encounters a new shader address not present in the `_graphicsSpirvCache`.

### What is the difference between SubmitTranslatedDraw and SubmitComputeDispatch?

`SubmitTranslatedDraw` handles rasterized graphics by accepting both vertex and pixel shader SPIR-V code along with primitive topology information, while `SubmitComputeDispatch` handles general-purpose GPU compute workloads using only a compute shader and dispatch dimensions (group counts). The former creates `VulkanTranslatedGuestDraw` objects, while the latter creates `VulkanComputeGuestDispatch` instances for the work queue.

### Does SharpEmu cache translated shaders between frames?

Yes. SharpEmu maintains `_graphicsSpirvCache` for vertex and pixel shaders and `_computeSpirvCache` for compute shaders. These dictionaries map the original PS4 shader addresses to compiled SPIR-V byte arrays. The system also uses `_shaderDigests` to identify identical shaders by hash, preventing redundant translation even when the same shader appears at different memory addresses.

### Where does the actual GPU execution happen in SharpEmu's code?

The actual Vulkan submission occurs in [`VulkanVideoPresenter.cs`](https://github.com/par274/sharpemu/blob/main/VulkanVideoPresenter.cs) within the background thread's `Run` method. This method constructs Vulkan command buffers, creates pipeline stages using `PipelineShaderStageCreateInfo`, binds descriptor sets for textures, and issues the final `vkQueueSubmit` call to the GPU driver. The public entry points `SubmitTranslatedDraw` and `SubmitComputeDispatch` merely enqueue work for this background thread.