Graphics
The 2D drawing layer opens the main output, hands you a framebuffer each frame, and gives a Surface
of shapes, text, sprites and image effects to draw into it — no shader compiler and no command buffers.
Everything on this page lives in SharpProspero.Graphics. When you need a movable, tiled scene or the
lower graphics-processor interface, see the child pages 2D scenes and
GPU command layer.
On this page
The display
DisplayDevice opens the main output, allocates its framebuffers from direct memory, registers them,
and presents frames, holding each until the output reports it on screen.
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 until the
output reports that flip as the one on screen, advances to the next framebuffer, and returns the
presented frame index. When the flip queue is full, Present retries instead of failing. The default is
a two-buffer swap chain; pass a higher bufferCount for triple buffering. The pixels are B8-G8-R8-A8
sRGB. Open takes only the sizes the output accepts — 1920x1080, 3840x2160, 720x480, 720x576, or a
width that is a multiple of 32 from 1280 to 1888 with a height of nine sixteenths of it — and throws
ArgumentOutOfRangeException for anything else. Only 1920x1080 is accepted on every console; the rest
need the console set up for them and are refused when the buffers are registered.
Open takes a tiling argument. The default, VideoOutTilingMode.Tiled, is accepted on any console
and is what the graphics processor draws into; BackBuffer is then a row-major surface of its own that
Present rearranges into the scan-out buffer, which walks every pixel on the processor. Pass
VideoOutTilingMode.Linear to draw straight into the scan-out buffer and skip that pass — the output
accepts it only while “Enhanced Display Buffer Attribute” is on in the machine’s debug settings, and
otherwise Open throws a ProsperoException saying so. Tiling reports which layout is in use.
For a caller that records its own flip on the graphics timeline, OutputHandle, CurrentBufferIndex,
BackBufferAddress and FrameIndex give the values the command buffer needs, and AdvanceFrame waits
for that frame and rotates the swap chain in place of Present. FlipStatus reports how far the output
has got through the flips submitted to it, and Present takes a VideoOutFlipMode (vertical sync by
default).
flowchart LR
A[Draw into BackBuffer] --> B[Present]
B --> C[Wait until that flip is on screen]
C --> D[Advance to next framebuffer]
D --> A
When an application runs under
ProsperoApp, the host owns the display and handsBackBufferto each frame throughFrameContext.Surface. You draw; you do not open or present the display yourself. See Application.
The surface
Surface is a lightweight view over a framebuffer. It holds a pointer and the geometry and allocates
nothing. Coordinates are in pixels with the origin at the top-left, and every operation clips 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. |
DrawTextOutlined(text, x, y, scale, fill, outline) |
Draw a string with a one-pixel outline, so it stays readable over a photo or a video frame. |
Surface.MeasureText(text, scale) |
Width in pixels the string occupies. Static: call it on the type, not on a surface. |
Fills use a single-pass span write per row, so Clear and FillRect run 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.
Extended drawing
The same surface carries a full set of shape, gradient and blit calls for finished interface work.
| 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. |
FillRadialGradient(x, y, w, h, center, edge) |
Fill a rectangle with a gradient from the middle out to the corners, for a soft background or a vignette. |
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). |
DrawTriangle(x0, y0, x1, y1, x2, y2, color) |
Draw a triangle outline. |
DrawPolyline(points, color, thickness) |
Draw connected line segments; the run is open, so the ends are not joined. |
DrawRectThick(x, y, w, h, thickness, color) |
Draw a rectangle outline of a given thickness, drawn inside the rectangle. |
FillEllipse(cx, cy, radiusX, radiusY, color) |
Fill an ellipse. |
DrawEllipse(cx, cy, radiusX, radiusY, color) |
Draw a one-pixel ellipse outline. |
FillArcRing(cx, cy, innerRadius, outerRadius, start, sweep, color) |
Fill a slice of a ring, measured clockwise from the positive x-axis; a full turn is a complete ring. |
FillPie(cx, cy, radius, start, sweep, color) |
Fill a pie slice (a sector of a disc). |
DrawArc(cx, cy, radius, start, sweep, color) |
Draw a one-pixel arc; a full turn is a complete circle. |
DrawCircleThick(cx, cy, radius, thickness, color) |
Draw a ring: a circle outline of a given thickness, drawn inwards. |
FillRectBlended(x, y, w, h, color) |
Fill a rectangle, blending by the color’s alpha (a translucent panel); opaque colors fill directly. |
FillCircleBlended(cx, cy, radius, color) |
Fill a disc, blending by the color’s alpha (a soft dot or glow). |
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. |
BlitScaledSmooth(source, x, y, w, h) |
Scaled copy with bilinear sampling, so an enlarged photo stays smooth rather than blocky. |
BlitRotated(source, centerX, centerY, angleRadians) |
Copy a surface rotated about its center, blended. |
BlitNineSlice(source, x, y, w, h, border) |
Draw a panel image at any size: corners keep their size, edges stretch along their run, the middle stretches both ways. |
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.
Text that fits
TextLayout fits text to a width: breaking a paragraph into lines, shortening a label that will not
fit, and drawing either one aligned. It measures through an ITextFont, so the same layout serves the
built-in text (BitmapTextFont) and a loaded outline font (TrueTypeFont implements the interface
too).
| Call | What it does |
|---|---|
Wrap(font, text, maxWidth) |
Break into lines no wider than the width, splitting at spaces. A line break starts a new line; a word too wide is split rather than overflowing. |
MeasureWrapped(font, text, maxWidth) |
The widest line and the total height of the wrapped block. |
DrawWrapped(surface, font, text, x, y, width, color, alignment) |
Draw the wrapped block; returns the height it used. |
DrawAligned(surface, font, text, x, y, width, color, alignment) |
Draw one line placed left, centre or right within a width. |
Truncate(font, text, maxWidth, ellipsis) |
Shorten to fit, ending with the marker when anything was dropped. |
alignment is a TextAlignment (Left, Center, Right) and defaults to Left.
ITextFont font = new BitmapTextFont(2);
int used = TextLayout.DrawWrapped(surface, font, description, 40, 100, 600, Color.White);
string name = TextLayout.Truncate(font, longFileName, columnWidth);
Sprite sheets and animation
SpriteSheet reads one image as a grid of equal-sized frames — a character’s animation, a set of
icons, a tile set — and draws any frame as a sprite. It reads frames as views onto the shared image and
copies nothing, so it allocates nothing of its own. Pair it with a frame counter, or with Tween in
SharpProspero.Animation (see animation.md), to drive it.
using var pngDec = SystemModule.Load(SystemModuleId.PngDec);
using var strip = PngImage.Decode(PackageFile.ReadAllBytes("/app0/run.png"));
var sheet = new SpriteSheet(strip.AsSurface(), frameWidth: 48, frameHeight: 48);
int frame = (int)(time * 12) % sheet.Count; // 12 frames a second
sheet.Draw(display.BackBuffer, frame, x, y); // blended, so a transparent background composites
| Member | What it is |
|---|---|
Columns, Rows, Count |
How the frames are arranged and how many there are. |
Frame(index) |
A view onto one frame (numbered left to right, then down), to draw however you like. |
Draw(dest, index, x, y) |
Draw a frame as a sprite, blended by alpha. |
DrawScaled(dest, index, x, y, w, h) |
Draw a frame scaled into a rectangle. |
AnimatedSprite plays a run of a sheet’s frames over time, so you do not track the frame yourself.
Point it at all of a sheet or a range within one — several animations can share a sheet — set the rate,
advance it each frame, and draw it.
var run = new AnimatedSprite(sheet, framesPerSecond: 12, firstFrame: 0, frameCount: 8);
// each frame:
run.Update((float)context.DeltaSeconds);
run.Draw(context.Surface, x, y);
Mode is a SpriteMode: Loop (the default), Once (stops on the last frame and reports
IsComplete) or PingPong (runs out and back). CurrentFrame is the sheet frame showing, LocalFrame
is the frame within the run, and Reset returns to the start.
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.Decode reads 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 with SystemModule
(in SharpProspero.Modules; see modules.md).
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, decodes into a fresh buffer, and releases its decoder. AsSurface views the
decoded pixels for drawing; disposing the image frees them. A decoded image carries its alpha channel,
so BlitBlended draws it over the background as a sprite while Blit copies it opaquely. JpegImage
decodes JPEG the same way (load SystemModuleId.JpegDec).
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
TgaImage and TgaEncoder read and write TGA, a simple lossless format that editors and asset
pipelines export, again with no module. Unlike BMP it keeps a proper alpha channel and reads
run-length-compressed files.
using var texture = TgaImage.Load("/app0/texture.tga"); // 24- or 32-bit, plain or RLE
display.BackBuffer.BlitBlended(texture.AsSurface(), x, y);
TgaEncoder.Save(display.BackBuffer, "/data/shot.tga"); // export 32-bit BGRA
GifImage.Decode reads GIF — static or animated — with no system module, so an interface can show an
animated icon or a spinner. It handles the common forms (GIF87a and GIF89a, global and local colour
tables, interlacing, transparency, and the frame-disposal methods) and returns fully-composed frames,
each with the delay to show it, so an animation is just the frames drawn in order.
using var gif = GifImage.Decode(PackageFile.ReadAllBytes("/app0/assets/spinner.gif"));
GifFrame frame = gif.Frames[frameIndex % gif.Frames.Count];
display.BackBuffer.BlitBlended(frame.AsSurface(), x, y);
Each GifFrame exposes AsSurface and DelayMilliseconds; the composed pixels carry transparency as an
alpha of zero, so BlitBlended overlays a frame while Blit copies it opaquely. LoopCount reports how
many times the animation repeats (0 for forever), and First is the only frame of a still GIF.
Off-screen buffers
PixelBuffer is a drawing surface of its own, off the screen: its pixels live in memory it owns, in the
same layout as the display. Draw into it as you would the back buffer, then blit it onto the screen. Use
it to build an image once and draw it many times (a pre-rendered sprite, a cached panel), to compose a
picture before showing it, or to build something to encode. It starts fully transparent, so a frame
drawn onto it with alpha composites cleanly.
using var cache = new PixelBuffer(256, 64);
Surface s = cache.AsSurface();
s.FillRoundedRect(0, 0, 256, 64, 12, theme.Panel);
s.DrawText("Ready", 16, 20, 3, Color.White);
// later, every frame — no redrawing the panel:
display.BackBuffer.BlitBlended(cache.AsSurface(), x, y);
Its AsSurface view is valid only while the buffer is alive, so dispose the buffer when you are done.
Color, gradients and palettes
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, and ToHsv reads them back for a color
picker or a hue shift. FromHsl and ToHsl do the same over hue, saturation and lightness, where
lightness runs black at 0 through the pure hue at 0.5 to white at 1 — often the easier space for
picking even tints and shades. Darken and Lighten move a color toward black or white while keeping its
alpha, for a pressed or hovered shade. 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.
Where the surface’s gradient fills blend two colors, a Gradient holds as many GradientStop entries
as you like and returns the color at any point along it — a heat ramp, a UI theme, a spectrum. A
Palette is a fixed set of colors addressed by index, which a gradient can fill by even sampling.
Color hot = Gradient.Heat.Sample(load); // 0..1 along black-red-yellow-white
var ramp = Gradient.Rainbow.ToPalette(16); // 16 evenly sampled colors
Color series = ramp.Cycle(seriesIndex); // wraps for a color-per-series scheme
Gradient sorts its stops, clamps the sample to 0-1, and blends the surrounding stops; TwoColor makes
a simple start-to-end ramp, and Heat and Rainbow are ready-made. Palette offers an index
(bounds-checked), Cycle (wraps any index), and Sample (maps 0-1 to the nearest entry).
Fonts
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, and bit 0 of a row is the leftmost column. This is what Surface.DrawText and BitmapTextFont
draw, scaled by an integer factor. It needs no file and no module, which makes it the ready choice for a
tool or an overlay.
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 top-left of the line, the same as every other font here, so the layout helpers place
both alike; DrawTextOnBaseline takes the baseline instead. 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");
PixelSize is settable, so one loaded font can be re-sized between draws; sizes below 1 or above 1024
pixels are pulled into that range. LineHeight is the distance from one line to the next and
BaselineOffset the distance from the top of a line down to its baseline, both as the font itself
reports them at the current size, so both move when the size changes. DrawText adds BaselineOffset
for you, which is how it takes a line top where DrawTextOnBaseline takes a baseline. Load also takes
memoryBudgetBytes, the size of the block reserved for the font; the default suits a UI font and
anything under 256 KB is refused.
TrueTypeFont and BitmapTextFont — the built-in glyphs wrapped as a font, new BitmapTextFont(scale) —
both implement ITextFont (LineHeight, MeasureText, DrawText), so TextLayout and the interface
controls work the same whichever one you choose. BitmapFont itself is only the glyph table
BitmapTextFont draws from.
Screenshots and photo export
A drawing surface encodes to a file for a screenshot or an export. PNG is lossless and best for interface captures; JPEG is far smaller and best for photographic content. Load the encode module first.
using var pngEnc = SystemModule.Load(SystemModuleId.PngEnc);
PngEncoder.Save(surface, "/data/screenshot.png");
using var jpegEnc = SystemModule.Load(SystemModuleId.JpegEnc);
JpegEncoder.Save(surface, "/data/photo.jpg", quality: 90); // quality 1..100
Both also return the encoded bytes directly — PngEncoder.Encode(surface) and
JpegEncoder.Encode(surface) — for sending over the network or storing elsewhere. This captures only
what the application itself drew; to capture the finished screen together with the system overlays, see
Content and capture.
Next
- 2D scenes — a camera, tile maps, particles and grid pathfinding built on this surface.
- GPU command layer — the lower graphics-processor interface, for shader-based rendering.