Graphics and memory

The display

DisplayDevice opens the main output, allocates its framebuffers from direct memory, registers them, and presents frames on the vertical blank.

using var display = DisplayDevice.Open(width: 1920, height: 1080, bufferCount: 2);
while (running)
{
    Surface surface = display.BackBuffer;
    surface.Clear(Color.Black);
    surface.DrawTextCentered("Frame", 500, 6, Color.White);
    display.Present();
}

BackBuffer is the framebuffer to draw the next frame into. Present submits it, waits for the vertical blank, and advances to the next framebuffer. The default is a two-buffer swap chain; pass a higher bufferCount for triple buffering. The buffers are B8-G8-R8-A8 sRGB in linear layout, which is the format the CPU renderer writes. The width must be a multiple of 64 so the row pitch matches the allocated framebuffer; the standard 1920 and 1280 widths already are.

When an application uses ProsperoApp, the host owns the display and hands BackBuffer to each frame through FrameContext.Surface; you do not open the display yourself.

The surface

Surface is a lightweight view over a framebuffer. It holds a pointer and the geometry and allocates nothing. Operations clip to the surface bounds.

Method Effect
Clear(color) Fill the whole surface.
SetPixel(x, y, color) Set one pixel; out-of-bounds is ignored.
FillRect(x, y, w, h, color) Fill a rectangle, clipped.
HLine(x, y, length, color) Draw a horizontal run, clipped.
VLine(x, y, length, color) Draw a vertical run, clipped.
DrawLine(x0, y0, x1, y1, color) Draw a one-pixel line between two points, clipped.
DrawRect(x, y, w, h, color) Draw a one-pixel rectangle outline, clipped.
FillCircle(cx, cy, radius, color) Fill a disc, clipped.
DrawCircle(cx, cy, radius, color) Draw a one-pixel circle outline, clipped.
Blit(source, x, y) Copy another surface onto this one, clipped.
BlitBlended(source, x, y) Copy another surface, blending each pixel over the destination by its alpha.
DrawGlyph(c, x, y, scale, color) Draw one glyph scaled by an integer factor.
DrawText(text, x, y, scale, color) Draw a string left to right.
DrawTextCentered(text, y, scale, color) Draw a string centered horizontally.
MeasureText(text, scale) Width in pixels the string occupies.

Fills use a single-pass span write per row, so Clear and FillRect are as fast as the memory allows. When a framebuffer’s row pitch is wider than the drawn width, construct the surface with a stride (in pixels) so addressing skips the padding while drawing still clips to the width.

More drawing

Method What it does
Region(x, y, w, h) A view over a sub-rectangle; drawing on it clips to the region, so it acts as a clip rectangle or a panel to draw inside.
FillVerticalGradient(x, y, w, h, top, bottom) Fill a rectangle with a top-to-bottom color gradient.
FillHorizontalGradient(x, y, w, h, left, right) Fill a rectangle with a left-to-right color gradient.
FillRoundedRect(x, y, w, h, radius, color) Fill a rectangle with rounded corners.
DrawRoundedRect(x, y, w, h, radius, color) Draw a rounded-rectangle outline.
DrawLine(x0, y0, x1, y1, color, thickness) Draw a line of a given pixel thickness.
FillTriangle(x0, y0, x1, y1, x2, y2, color) Fill a triangle.
FillPolygon(points, color) Fill a simple polygon (even-odd rule).
BlitScaled(source, x, y, w, h) Copy a surface scaled to a destination rectangle.
BlitScaledBlended(source, x, y, w, h) Scaled copy, blending by source alpha.
BlitRotated(source, centerX, centerY, angleRadians) Copy a surface rotated about its center, blended.

Region composes: build a themed panel by taking a sub-region and drawing into it with its own local coordinates. Gradients and rounded rectangles give buttons and panels a finished look without an image, and the scaled and rotated copies place thumbnails and sprites.

Image effects

A surface also transforms in place, over its own pixels, so the same calls apply to a decoded image, an off-screen surface, or the back buffer. Each keeps the alpha channel.

Method What it does
Invert() Invert the colours (a photo negative).
ToGrayscale() Convert to grey by perceived luminance.
AdjustBrightness(delta) Add to every channel (negative darkens), clamped.
AdjustContrast(factor) Scale contrast around mid-grey (1 leaves it, above 1 raises it).
Tint(color, amount) Blend towards a colour for a wash.
FlipHorizontal() / FlipVertical() Mirror left-to-right or top-to-bottom.
BoxBlur(radius) Blur with a box filter of the given radius.
using var photo = BmpImage.Load("/data/photo.bmp");
Surface s = photo.AsSurface();
s.AdjustBrightness(20);
s.BoxBlur(2);
display.BackBuffer.Blit(s, 0, 0);

Images

PngImage decodes a PNG into B8-G8-R8-A8 pixels — the same layout the display surface uses — so a decoded image blits straight onto a framebuffer. Load the decode module first.

using var pngDec = SystemModule.Load(SystemModuleId.PngDec);
byte[] bytes = PackageFile.ReadAllBytes("/app0/assets/logo.png");
using var logo = PngImage.Decode(bytes);
display.BackBuffer.Blit(logo.AsSurface(), x: 100, y: 100);

Decode parses the header, sizes and creates a decoder, decodes into a fresh buffer, and releases the decoder. AsSurface views the decoded pixels for drawing; disposing the image frees them. JpegImage decodes JPEG the same way (load SystemModuleId.JpegDec). A decoded image carries its alpha channel, so BlitBlended draws it over the background as a sprite while Blit copies it opaquely.

BmpImage reads BMP, and BmpEncoder writes it, with no system module — the format is uncompressed, so the SDK handles it on its own. It is a dependable interchange format for a file browser or an editor, and a fallback when no decode module is loaded.

using var picture = BmpImage.Load("/data/picture.bmp");   // 24- or 32-bit BMP
display.BackBuffer.Blit(picture.AsSurface(), 0, 0);

BmpEncoder.Save(display.BackBuffer, "/data/shot.bmp");     // export a 24-bit BMP

Color

Color packs a pixel for the display format. In memory the bytes run blue, green, red, alpha. The A, R, G and B properties read the channels back.

Color background = Color.FromRgb(0x0E, 0x11, 0x16);
Color translucent = Color.FromArgb(0x80, 0xFF, 0x00, 0x00);
Color midway = Color.Lerp(Color.Black, Color.White, 0.5f);   // blend between two colors
Color rainbow = Color.FromHsv(context.TotalSeconds * 60 % 360, 1f, 1f);   // hue over time

Lerp blends component-wise with the factor clamped to 0-1. FromHsv builds an opaque color from a hue in degrees (wrapped) and a saturation and value in 0-1. WithAlpha keeps the red, green and blue and sets a new alpha, which BlitBlended then composites. Black, White, Red, Green, Blue and Transparent are ready to use.

The font

BitmapFont carries an 8x8 monospaced font for printable ASCII (0x20-0x7F) as read-only data. GetGlyph(c) returns the eight rows for a character; anything outside the range maps to the blank space glyph. Bit 0 of a row is the leftmost column. Scale glyphs with an integer factor when drawing.

Scalable text

For smooth text at any size, TrueTypeFont loads a .ttf or .otf file and renders antialiased glyphs in any color. Load the font modules first, load a font from its bytes, set the pixel size, and draw. (x, y) is the left end of the text baseline. Dispose the font when done.

using var module = SystemModule.Load(SystemModuleId.Font);
using var backend = SystemModule.Load(SystemModuleId.FontFt);

byte[] ttf = FileSystem.ReadAllBytes("/app0/assets/font.otf");
using var font = TrueTypeFont.Load(ttf, pixelSize: 32);

font.DrawText(surface, "Hello, world", 100, 200, Color.White);
int width = font.MeasureText("Hello, world");

Direct memory

GPU-visible buffers come from direct memory, not the managed heap. DirectMemoryRegion reserves, maps and releases a region in one disposable object.

using var region = DirectMemoryRegion.Allocate(bytes: 8u * 1024 * 1024);
Surface surface = region.AsSurface(1920, 1080);

Allocate rounds the size up to the alignment (2 MiB by default), reserves cached memory shared between the CPU and GPU, and maps it CPU-readable, CPU-writable and GPU-readable. Override the type, protection and alignment for other uses. Dispose releases the reservation; the region is released once and is safe to dispose more than once.

The managed heap

The application runs with a small, non-concurrent collector and a hard ceiling baked into the image. Set the ceiling per project with ProsperoHeapHardLimitBytes. Because memory maps are limited, keep per-frame allocation flat:

  • Draw into pre-allocated framebuffers rather than new buffers.
  • Reuse arrays and objects across frames; the frame context is already reused for you.
  • Prefer stackalloc and pointers for short-lived unmanaged buffers, as the bindings do.

HeapMonitor reads usage so a loop can react before it reaches the ceiling:

if (HeapMonitor.ExceedsBudget(0.85))
    HeapMonitor.Collect();

Capture returns a HeapSnapshot with the committed heap size, total allocated bytes, the ceiling and the collection count, plus a Pressure ratio. Run Collect sparingly, for example after loading a scene rather than every frame.

Timing

GameClock is a monotonic clock in the SharpProspero.Timing namespace. Construct one to measure from a fixed origin, or use its static members for one-off readings and sleeping.

var clock = new GameClock();
// ... work ...
double seconds = clock.ElapsedSeconds;
GameClock.Sleep(TimeSpan.FromMilliseconds(2));

ElapsedMicroseconds and ElapsedSeconds count from construction; Restart moves the origin to now. GameClock.ProcessMicroseconds reads the microseconds since the process started. The readings never move backward.

For the calendar date and time, SystemClock reads the real-time clock and returns a DateTime:

DateTime utc = SystemClock.UtcNow;
DateTime local = SystemClock.LocalNow;

Use GameClock to pace and measure frames and SystemClock for the wall-clock time; unlike the game clock, the system clock follows the calendar and can jump when the clock is set.

Files

PackageFile in SharpProspero.Storage reads files bundled with a module. Assets live under the package root (PackageFile.Root, /app0).

byte[] level = PackageFile.ReadAllBytes("/app0/assets/level.bin");
string config = PackageFile.ReadAllText("/app0/config.json");

ReadAllBytes opens, sizes, reads and closes the file in one call and throws a ProsperoException on failure. For finer control, the KernelFile bindings expose open, read, write, seek and close directly.

To browse or change files, FileSystem lists a directory and creates, moves and removes entries:

foreach (DirectoryEntry entry in FileSystem.EnumerateDirectory("/app0/assets"))
{
    string kind = entry.IsDirectory ? "dir " : "file";
    long size = entry.IsFile ? FileSystem.GetFileSize($"/app0/assets/{entry.Name}") : 0;
    surface.DrawText($"{kind} {entry.Name} {size}", x, y, 2, Color.White);
}

EnumerateDirectory returns each entry’s Name and Type (IsDirectory and IsFile cover the common cases) and leaves out . and ... GetFileSize, Exists, CreateDirectory, DeleteFile, DeleteDirectory, Move, ReadAllBytes, WriteAllBytes and WriteAllText round it out. The package root /app0 is read-only; writes need a writable mount.

Random

SharpProspero.Numerics has two random sources. GameRandom is a fast, reproducible generator for gameplay; HardwareEntropy draws unpredictable bytes from the system for seeds.

var rng = new GameRandom(seed: 1234);   // same seed, same sequence
int roll = rng.Next(1, 7);              // 1..6
double t = rng.NextDouble();            // 0..1

var unpredictable = GameRandom.FromEntropy();       // seeded from the system
ulong token = HardwareEntropy.NextUInt64();         // straight from the entropy source

GameRandom gives NextUInt64, NextUInt32, NextDouble (0 to 1) and Next(min, max) (max exclusive). It is for gameplay, not for keys or tokens; take those from HardwareEntropy.

Playing media

SharpProspero.Media.MediaPlayer plays a media file. Open it for a path, start it, then pull decoded audio frames to push at an audio port and video frames to draw to the display, while it stays active.

using var player = MediaPlayer.Open("/app0/movie.mp4");
using var audio = AudioOutDevice.OpenStereo();
player.Start();
while (player.IsActive)
{
    if (player.TryGetAudioFrame(out AudioFrame audioFrame))
        audio.Output(audioFrame.Samples);
    if (player.TryGetVideoFrame(out VideoFrame videoFrame))
        videoFrame.RenderTo(display.BackBuffer, 0, 0, display.Width, display.Height);
}

Start, Stop, Pause, Resume, SetLooping, JumpTo and Position control playback. TryGetAudioFrame and TryGetVideoFrame return false when nothing is decoded yet, which is normal; each audio frame reports its samples, timestamp, channel count and sample rate, and each video frame is a decoded picture. VideoFrame.RenderTo converts the frame to the surface color and scales it to the destination rectangle, so a movie draws full-screen or in a window.

The player decodes on its own threads and calls back for every allocation, which the SDK answers from the unmanaged heap. It plays a path itself, so no file callbacks are needed.

MediaPlayer.OpenUrl plays a network stream instead of a file — pass an http:// or https:// address and the player opens the stream over its own network source. The console needs a working connection; everything after opening is the same as a file.

using var player = MediaPlayer.OpenUrl("https://example.com/stream.m3u8");
player.Start();

The web browser

SharpProspero.Platform.WebBrowser opens the system browser over the running application. Open it for an address, then poll it once per frame until it closes.

using var browser = WebBrowser.Open("https://example.com");
while (browser.Update() != WebBrowserState.Closed)
    display.Present();
int result = browser.Result();

Opening brings the shared dialog subsystem up, loads the browser module, and starts it, in that order; disposing closes the browser if it is still open and shuts the subsystem down. The parameter block the service takes carries a check value derived from its own address, so build it with WebBrowserDialog.InitializeParam and hand it over from where it lives; do not copy it afterwards.

Text input

SharpProspero.Platform.TextInputDialog shows the on-screen keyboard and hands back what the user typed. Open it for a title, poll it once per frame until it closes, then read the text. This is what lets a file explorer name a folder, a browser take an address, or any utility accept input.

using var input = TextInputDialog.Open("Enter a name", maxLength: 64);
while (input.Update() == TextInputState.Running)
    display.Present();
if (input.EndStatus == ImeDialogEndStatus.Ok)
    Use(input.Text);

Open brings the dialog subsystem up, loads the keyboard module, and shows the keyboard centered on screen. Pass a placeholder, an initialText, an ImeType (a number pad, an email or web-address layout, and so on), or ImeOption.Password to mask the text. Text is the entered string once the keyboard has finished, or empty when the user canceled. Disposing closes the keyboard, shuts it down, unloads the module, and releases the text buffer.

Keyboard and mouse

SharpProspero.Input.Keyboard and SharpProspero.Input.Mouse read a USB keyboard and mouse, the input a file explorer or a browser wants beyond the controller. Open each for a user, read it each frame, dispose it at shutdown.

using var keyboard = Keyboard.Open();
using var mouse = Mouse.Open();

KeyboardState keys = keyboard.Read();
if (keys.Modifiers.HasFlag(KeyModifier.LeftControl)) { }

MouseState m = mouse.Read();
cursorX += m.DeltaX;              // the mouse reports movement, not a position
cursorY += m.DeltaY;
if (m.IsButtonDown(MouseButton.Primary)) { }

The keyboard reports the USB usage codes of the keys held and the modifier state; the mouse reports the movement since the last read, the buttons, and the wheel.

Network information

SharpProspero.Platform.NetworkInfo reports the network connection, the panel a system-information utility shows. Open it, read the fields, dispose it.

using var net = NetworkInfo.Open();
if (net.IsConnected)
{
    Show(net.IpAddress);            // "192.168.1.20"
    Show(net.Ssid);                // wireless network name, empty when wired
    Show(net.MacAddress);          // "00:1a:2b:c0:ff:ee"
    Show(net.SignalStrength);      // 0 to 100 on wireless
}

Device reports wired or wireless; State reports where the connection is. Opening needs no socket pool; the status service is the first and only network call it makes.

Message and error dialogs

SharpProspero.Platform.MessageDialog shows a message with buttons, or a progress bar the application drives — the progress bar is what a package installer shows while it works.

// A progress bar, driven by the application:
using var progress = MessageDialog.ShowProgress("Installing...");
while (installing)
    progress.SetProgress(percentDone);

// A yes/no question:
using var ask = MessageDialog.ShowMessage("Delete this file?", MessageDialogButtons.YesNo);
while (ask.Update() == MessageDialogState.Running)
    display.Present();
bool yes = ask.ChosenButton == MsgDialogButtonId.Ok;   // OK and Yes share the first button

SharpProspero.Platform.ErrorDialog shows the console’s own message for an error code, to report a failure in the system’s style. Show it, poll it until it closes.

using var dialog = ErrorDialog.Show(errorCode);
while (dialog.Update() != ErrorDialogState.Closed)
    display.Present();

Save data

SharpProspero.Platform.SaveDataManager lists and manages the console’s saves for the signed-in user, the surface a save-data manager or a backup tool is built on.

using var saves = SaveDataManager.Open();
foreach (SaveDataInfo save in saves.Enumerate("CUSA00000"))
    Show(save.Title, save.SubTitle, save.ModifiedTime);

// Mount one to read its files, then unmount:
using MountedSave mounted = saves.Mount(saves.Enumerate("CUSA00000")[0].DirName);
byte[] data = FileSystem.ReadAllBytes(mounted.MountPoint + "/progress.dat");

Enumerate returns each save’s directory name, title, subtitle, detail, user parameter, and modified time. Mount mounts a save read-only by default and returns the path its files live under; Delete removes a save.

Downloading

SharpProspero.Platform.HttpClient downloads over HTTP and HTTPS, to fetch a file or a package from a URL. Creating it brings up the network pool, the TLS context, and the HTTP service in order.

using var http = HttpClient.Create();
HttpResponse response = http.Get("https://example.com/homebrew.pkg");
if (response.IsSuccess)
    FileSystem.WriteAllBytes("/data/homebrew.pkg", response.Body);

Get returns the status code and the body. Combined with the package installer, this downloads and installs a package from the network.

Notifications

SharpProspero.Platform.Notification shows the on-screen toast that slides in at the top of the screen — to confirm a copy, report a finished install, or show a short message.

Notification.Show("Installed successfully.");

Install progress and app parameters

SharpProspero.Platform.PlayGo reads how much of the application’s content has downloaded, for a launcher or a title that streams its data.

using var playGo = PlayGo.Open();
DownloadProgress p = playGo.GetProgress(new ushort[] { 0, 1, 2 });
Show(p.Fraction);

SharpProspero.Platform.AppContent reads the parameters a title was packaged with:

int level = AppContent.GetIntParam(1);   // user-defined parameter 1

Reading the system

SharpProspero.Platform.SystemInfo reports facts about the console. A diagnostics or settings utility shows the system software version the way the console displays it:

string firmware = SystemInfo.SystemSoftwareVersion;   // for example "11.020.000"

SystemSoftwareVersionValue returns the same version packed into a word (major byte then minor byte, as it reads), which is the form a package’s requirement is compared against. ConsoleId returns the console’s open identifier as a hex string, and ProcessorCount returns the number of cores available to the application.

Installing a package

SharpProspero.Platform.PackageInstaller installs a package file. The install service is not part of the module set a title links against, so it is loaded at run time and its entry points resolved by name.

using var installer = PackageInstaller.Open();
installer.Install("/data/homebrew.pkg");

Open loads the service and starts it; Install hands the request over and the install continues in the background; disposing shuts the service down. A missing module, a missing entry point, or a rejected request all raise a ProsperoException. AppExists("CUSA00000") reports whether a title is installed, and AppGetSize("CUSA00000") reads its installed size in bytes — an app manager lists and inspects installed titles with these.

System settings

SharpProspero.Platform.SystemParameters reads the user’s console settings so a title can match them:

if (SystemParameters.Language == SystemLanguage.French)
    LoadStrings("fr");
int minutesFromUtc = SystemParameters.TimeZoneMinutes;

Language is a SystemLanguage, DateFormat and TimeFormat describe how to present dates and times, TimeZoneMinutes is the offset from UTC, and IsSummerTime reports daylight saving.

Audio

AudioOutDevice in SharpProspero.Audio opens a stereo 16-bit output port. Fill a buffer of SamplesPerBlock interleaved samples (left, right, left, right, …) and push it; each push blocks until the block plays, which paces the caller to the audio clock.

using var audio = AudioOutDevice.OpenStereo(grain: 256, sampleRate: 48000);
short[] block = new short[audio.SamplesPerBlock];
while (running)
{
    FillBlock(block);      // your synthesis or streaming
    audio.Output(block);
}

OpenStereo takes a grain (samples per block, 256 to 2048) and a sample rate. SetVolume sets both channels from 0 to AudioOut.Volume0Db. Disposing closes the port.

Controller output

GamePad drives the controller’s motors and light bar alongside reading input:

gamePad.SetVibration(largeMotor: 200, smallMotor: 120);
gamePad.SetLightBar(0x00, 0x80, 0xFF);
gamePad.ResetLightBar();

Each returns false when the controller does not accept the request. Disposing the pad stops the motors.

Controller motion and touch

A controller sample carries more than buttons and sticks. GamePadState also decodes the motion and touch fields, so a title can read the controller’s orientation, motion and touch pad:

GamePadState pad = context.Input;
Vector3 tilt = pad.AngularVelocity;       // radians per second
Quaternion facing = pad.Orientation;      // accumulated orientation
if (pad.Touch1.IsActive)
    DrawCursor(pad.Touch1.X, pad.Touch1.Y);

Orientation is a quaternion, Acceleration is in G and AngularVelocity is in radians per second, each a System.Numerics value. Touch1 and Touch2 are the two touch-pad contacts, each with a position and a tracking id, and TouchCount reports how many are live. IsConnected and TimestampMicroseconds describe the sample itself.

System modules

Some device libraries are not resident by default; load the ones a title needs at startup with SystemModule and dispose them at shutdown.

using var pngDec = SystemModule.Load(SystemModuleId.PngDec);
// ... use the module ...

SystemModule.IsLoaded(id) reports whether a module is present. Loading an already-loaded module succeeds.

System-version range

Bindings target the earliest supported system version, and later systems keep every earlier function, so a module built with the SDK installs and runs across the whole version range. Nothing in the build needs to state a version for that to hold.

The one place a version is declared is the package: put the lowest system the title needs in sce_sys/param.json as requiredSystemSoftwareVersion. Raise it when the title calls a function a later system added. The module itself records no system version, so this is what gates installation.

Versions do matter in one other place, and the toolchain handles it: a module records the module and library version of everything it imports, and the loader binds an import only when that version matches what the providing module publishes. See modules.md for what that means when you link against a library of your own.


Copyright © SvenGDK 2026. Distributed under the GPL-3.0-or-later license.

This site uses Just the Docs, a documentation theme for Jekyll.