# sceVideoOut Implementation in SharpEmu: How the PS5 Video API Is Emulated

> Discover how SharpEmu emulates the PS5 sceVideoOut API using C#. Explore Vulkan surface mapping, V-blank timing, and buffer management in this technical deep dive.

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

---

**SharpEmu implements the PlayStation 5 sceVideoOut API as a high-level emulation (HLE) layer in the `VideoOutExports` class, mapping guest video ports to host Vulkan surfaces while emulating V-blank timing and buffer management in pure C#.**

The `sceVideoOut` library (`libSceVideoOut`) is the core display interface used by PlayStation 5 games to render frames. In the open-source emulator SharpEmu (`par274/sharpemu`), this proprietary Sony API is reimplemented as managed C# code that bridges guest PS5 applications to the host PC's graphics hardware. This article examines the architecture, synchronization mechanisms, and GPU integration used to emulate video output.

## Core Architecture of the sceVideoOut HLE Layer

All exported functions are defined in the static class **`VideoOutExports`** located in [`src/SharpEmu.Libs/VideoOut/VideoOutExports.cs`](https://github.com/par274/sharpemu/blob/main/src/SharpEmu.Libs/VideoOut/VideoOutExports.cs). Each function is decorated with the **`[SysAbiExport]`** attribute, allowing the emulator's HLE layer to locate them by their NID (Numeric Identifier) and expose them to guest code.

### Port Management and Handle Allocation

The emulator maintains a global dictionary **`_ports`** that maps numeric handles to **`VideoOutPortState`** instances. When a game calls `sceVideoOutOpen`, the implementation validates the bus type (typically `SceVideoOutBusTypeMain`) and user ID, then allocates a unique handle via `_nextHandle++` before inserting a new state entry.

```csharp
// sceVideoOutOpen implementation
// https://github.com/par274/sharpemu/blob/main/src/SharpEmu.Libs/VideoOut/VideoOutExports.cs#L199-L236
public static int sceVideoOutOpen(int userId, int busType, int index)
{
    // Validation and handle allocation logic
    var portState = new VideoOutPortState { ... };
    _ports[handle] = portState;
    return handle;
}

// sceVideoOutClose removes the entry
// https://github.com/par274/sharpemu/blob/main/src/SharpEmu.Libs/VideoOut/VideoOutExports.cs#L242-L252

```

Closing a port via `sceVideoOutClose` simply removes the corresponding entry from the dictionary and disposes associated resources.

### Thread-Safe State Synchronization

All mutable state is guarded by the **`_stateGate`** lock object. This ensures that simultaneous guest threads cannot corrupt port data while the emulator's background V-blank pump (`_vblankPumpTimer`) iterates over active ports. Without this synchronization, race conditions between the guest application submitting flips and the emulator's timing thread could corrupt the `VblankCount` or `FlipCount` registers.

### The V-Blank Pump and Event Signaling

A periodic **`Timer`** (approximately 16ms, simulating 60Hz) drives the **`PumpVblanks()`** method. This timer calls `KernelEventQueueCompatExports.TriggerDisplayEvent` for each port that has registered V-blank events via `sceVideoOutAddVblankEvent`.

```csharp
// PumpVblanks implementation
// https://github.com/par274/sharpemu/blob/main/src/SharpEmu.Libs/VideoOut/VideoOutExports.cs#L68-L99
private static void PumpVblanks(object state)
{
    lock (_stateGate)
    {
        foreach (var port in _ports.Values)
        {
            if (port.HasVblankEvent)
            {
                port.VblankCount++;
                TriggerDisplayEvent(port, eventType: 0);
            }
        }
    }
}

```

The pump updates a per-port **`VblankCount`** used to encode event hints for the guest application's event queue.

## Buffer Registration and Pixel Format Handling

### Buffer Groups and Slot Mapping

The implementation abstracts guest memory buffers into two structures: **`VideoOutBufferGroup`** (holding metadata like pixel format, tiling mode, and dimensions) and **`VideoOutBufferSlot`** (mapping specific guest addresses to group indices).

When `sceVideoOutRegisterBuffers` or `RegisterBufferRange` is called, the emulator:

1. Creates a new `VideoOutBufferGroup` with the specified `BufferAttribute`
2. Maps guest physical addresses to `VideoOutBufferSlot` entries
3. Updates the port's output dimensions
4. Primes the **`VulkanVideoPresenter`** to accept the images

```csharp
// RegisterBufferRange implementation
// https://github.com/par274/sharpemu/blob/main/src/SharpEmu.Libs/VideoOut/VideoOutExports.cs#L556-L610
public static int RegisterBufferRange(int handle, ulong guestAddress, int size, ref BufferAttribute attr)
{
    // Creates buffer groups and notifies Vulkan presenter
    presenter.EnsureStarted(attr.Width, attr.Height);
    presenter.RegisterKnownDisplayBuffers(...);
}

```

### Pixel Format Normalization

The PS5 defines several 32-bit and 64-bit pixel formats. SharpEmu normalizes these via **`NormalizePixelFormat`**, then maps them to Vulkan guest texture formats using **`MapPixelFormatToGuestTextureFormat`**. Conversion helpers like **`ConvertRowToRgb`** handle format translation when necessary.

```csharp
// Pixel-format normalization logic
// https://github.com/par274/sharpemu/blob/main/src/SharpEmu.Libs/VideoOut/VideoOutExports.cs#L1450-L1466
private static PixelFormat NormalizePixelFormat(uint ps5Format)
{
    // Maps PS5 format constants to internal enum
}

```

## Flip Submission and GPU Presentation

### Submitting Frames with sceVideoOutSubmitFlip

When a game calls `sceVideoOutSubmitFlip`, the implementation verifies the buffer index, updates the port's **`CurrentBuffer`** and **`FlipCount`**, and builds an event hint for the flip event queue. If the buffer is valid, the image is forwarded to the GPU presenter.

```csharp
// SubmitFlip implementation
// https://github.com/par274/sharpemu/blob/main/src/SharpEmu.Libs/VideoOut/VideoOutExports.cs#L540-L574
public static int sceVideoOutSubmitFlip(int handle, int bufferIndex, int flipMode, long flipArg)
{
    // Validation and state update
    port.CurrentBuffer = bufferIndex;
    port.FlipCount++;
    
    // Notify flip event queues
    TriggerFlipEvent(port, flipArg);
    
    // Forward to GPU presenter
    vulkanPresenter.TrySubmitGuestImage(...);
}

```

### Vulkan Integration via VulkanVideoPresenter

Actual rendering is delegated to **`VulkanVideoPresenter`** ([`src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.cs`](https://github.com/par274/sharpemu/blob/main/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.cs)). This class bridges the software-only video-out implementation with the host GPU. During a flip, the presenter either:

- **Submits a guest image** via `VulkanVideoPresenter.TrySubmitGuestImage` (GPU path)
- **Submits a host-generated RGBA frame** via `SubmitHostRgbaFrame` (used for internal frame capture)

The presenter creates Vulkan swap chains and manages the host window's surface, translating the emulated PS5 display pipeline into Vulkan API calls.

## Debugging and Diagnostics Features

SharpEmu includes extensive diagnostics for the video-out path. When the environment variable **`SHARPEMU_DUMP_VIDEOOUT=1`** is set, the emulator writes each submitted frame to disk as BMP or raw data. The **`TryDumpFrame`** method uses fingerprint-based duplicate suppression to avoid spamming logs with identical frames.

```csharp
// Frame dumping logic
// https://github.com/par274/sharpemu/blob/main/src/SharpEmu.Libs/VideoOut/VideoOutExports.cs#L559-L639
private static void TryDumpFrame(VideoOutPortState port, int bufferIndex)
{
    if (Environment.GetEnvironmentVariable("SHARPEMU_DUMP_VIDEOOUT") == "1")
    {
        // BMP serialization with deduplication
    }
}

```

## Complete Code Example: Guest Workflow

The following C# pseudocode mirrors the sequence a PS5 game executes to initialize video output and submit frames:

```csharp
// 1. Open the main video-out port
int handle = sceVideoOutOpen(userId: 0, busType: 0, index: 0);

// 2. Configure vsync (1 = vsync, 0 = immediate, 2 = double-vsync)
sceVideoOutSetFlipRate(handle, rate: 1);

// 3. Describe buffer format (A8R8G8B8_SRGB, linear)
var attr = new BufferAttribute {
    PixelFormat = 0x80000000,
    TilingMode = 1, // Linear
    Width = 1920,
    Height = 1080,
    PitchInPixel = 1920
};
sceVideoOutSetBufferAttribute(handle, ref attr);

// 4. Register guest memory buffers (double buffering)
ulong[] bufferAddresses = { 0x10000000, 0x20000000 };
sceVideoOutRegisterBuffers(handle, startIndex: 0, bufferAddresses, bufferNum: 2, ref attr);

// 5. Register for flip events (optional)
sceVideoOutAddFlipEvent(equeue, handle, userData: 0);

// 6. Submit flips during render loop
sceVideoOutSubmitFlip(handle, bufferIndex: 0, flipMode: 0, flipArg: frameCounter);

// 7. Block until next V-blank (optional)
sceVideoOutWaitVblank(handle);

// 8. Cleanup on exit
sceVideoOutClose(handle);

```

All calls correspond to the actual implementations in [`VideoOutExports.cs`](https://github.com/par274/sharpemu/blob/main/VideoOutExports.cs) referenced earlier.

## Summary

- **Port Management**: SharpEmu tracks video-out ports using a thread-safe dictionary mapping handles to `VideoOutPortState` objects, with lifecycle managed by `sceVideoOutOpen` and `sceVideoOutClose`.
- **Synchronization**: The `_stateGate` lock protects mutable state from concurrent access by guest threads and the 60Hz V-blank pump timer.
- **Buffer Abstraction**: `VideoOutBufferGroup` and `VideoOutBufferSlot` structures manage guest memory registration, pixel format normalization, and mapping to Vulkan textures.
- **GPU Presentation**: The `VulkanVideoPresenter` class bridges the HLE layer to the host GPU, submitting guest images via Vulkan while supporting both immediate and vsync flip modes.
- **Debugging**: Frame dumping via environment variables and extensive logging through `SharpEmuLogger` provide visibility into the video-out pipeline.

## Frequently Asked Questions

### What is sceVideoOut in the context of PS5 emulation?

**sceVideoOut** is the PlayStation 5's native display library that manages screen output, buffer swapping, and V-sync timing. In SharpEmu, it is implemented as a high-level emulation (HLE) layer that intercepts guest函数 calls and translates them into host graphics API operations, allowing PS5 games to render without requiring actual PS5 video hardware.

### How does SharpEmu handle V-sync and timing?

SharpEmu emulates V-sync using a background `Timer` that fires approximately every 16ms (60Hz) to simulate the hardware V-blank interrupt. The **`PumpVblanks`** method increments per-port counters and signals kernel event queues, while `sceVideoOutSetFlipRate` allows games to select immediate presentation, single V-sync, or double V-sync (30Hz) modes.

### Where is the actual rendering performed in SharpEmu?

While the `VideoOutExports` class manages the emulated state and buffer metadata, actual pixel rendering occurs in **`VulkanVideoPresenter`** ([`VulkanVideoPresenter.cs`](https://github.com/par274/sharpemu/blob/main/VulkanVideoPresenter.cs)). This class creates the host window, manages Vulkan swap chains, and either displays guest-rendered textures or host-generated frames, effectively replacing the PS5's display hardware with PC GPU capabilities.

### How can developers debug video output issues in SharpEmu?

Developers can enable frame dumping by setting the environment variable **`SHARPEMU_DUMP_VIDEOOUT=1`**, which writes each submitted frame to disk in BMP format with duplicate detection. Additionally, the `TraceVideoOut` logging channel in [`SharpEmuLogger.cs`](https://github.com/par274/sharpemu/blob/main/SharpEmuLogger.cs) provides detailed traces of every `sceVideoOut` function call, buffer registration, and flip submission for step-by-step debugging of the display pipeline.