Quick answer: Surfaces created with surface_create contain uninitialised GPU memory and appear filled with random colour. Surfaces freed with surface_free leave a stale handle that may render previous content if reused without checking surface_exists. Always check surface_exists before drawing, set the surface as render target then draw_clear_alpha to wipe to transparent, and recreate in Draw GUI Begin before anything reads from it.

Here is how to fix GameMaker surfaces that draw stale or garbage content after recreation. Your minimap renders to a 256x256 surface, the player resizes the window, your code calls surface_free and surface_create with the new dimensions, and the next frame shows the old minimap squashed at the wrong scale, or worse — a wall of green-and-magenta noise that looks like a shader exploded. The recreation happened, the surface is the right size, but its contents are not what you drew. Either you are reading uninitialised VRAM, or you are reading through a handle that no longer points where you think it does.

The Symptom

After freeing and recreating a surface, the next draw shows incorrect contents. The exact symptom depends on driver and platform, but they all stem from the surface containing data other than what your code wrote.

Random colour noise. The surface displays speckled bands of green, magenta, and white — the classic appearance of uninitialised GPU memory. This is most visible on the very first draw after creation when the surface has not yet been written to.

Old contents persist. The surface displays what it contained before being freed, even though you called surface_free in between. This happens when the GPU recycles the same memory region for the new surface and you draw it before clearing.

Half-rendered frame. The surface shows partial content — the bottom half is what you drew this frame, the top half is what was in VRAM before. The render target was set, drawing started, but the surface was not cleared first so the un-touched area still holds garbage.

Wrong size with stretching. The new surface is created at one size but draws content sized for the previous dimensions. This indicates the draw call ran with the old surface dimensions cached somewhere, then targeted the new surface, producing a stretch.

Application loses the surface entirely. The surface goes black on Alt-Tab, device wake, or window minimise/restore. The GPU dropped the surface contents during a context loss event and the code did not detect the loss before drawing.

What Causes This

Uninitialised GPU memory. surface_create allocates a chunk of VRAM but does not zero or clear it. Whatever data was previously stored in that memory region is what the surface contains until you write to it. On modern GPUs this manifests as colour noise; on simpler hardware it may be black or white but is never guaranteed.

Stale surface handle. The variable that holds the surface is an integer index into the engine’s surface table. After surface_free the index is no longer valid, but the integer in your variable is unchanged. If you re-pass it to a draw function without checking surface_exists, the engine looks up that index. On some platforms it returns nothing; on others it may resolve to a different surface that has been allocated in the same slot, producing visually incorrect but technically “successful” draws.

Draw event runs before recreation. If you recreate the surface inside the Step event after some Draw events have already started referencing it (through deferred draw queues, particle emitters, or shader effects that read previous frame data), the recreation happens mid-frame and downstream draws get an incomplete surface.

Application context loss. Mobile platforms and some Windows configurations release GPU resources when the app is backgrounded. Surfaces are dropped without notice. When the app returns the handle is still in your variable but the surface no longer exists. surface_exists returns false in this case — the diagnostic that catches this scenario.

The Fix

Step 1: Check surface_exists and clear immediately after create. Wrap every use of the surface in a guard that recreates and clears if necessary. This handles both first-time creation and any subsequent loss.

// obj_minimap Draw GUI Begin event
var _w = 256;
var _h = 256;

if (!surface_exists(minimap_surf)) {
    minimap_surf = surface_create(_w, _h);

    // Clear the freshly allocated surface to transparent
    surface_set_target(minimap_surf);
    draw_clear_alpha(c_black, 0);
    surface_reset_target();

    last_w = _w;
    last_h = _h;
}

// Detect resize and recreate if dimensions changed
if (last_w != _w || last_h != _h) {
    surface_free(minimap_surf);
    minimap_surf = surface_create(_w, _h);
    surface_set_target(minimap_surf);
    draw_clear_alpha(c_black, 0);
    surface_reset_target();
    last_w = _w;
    last_h = _h;
}

The last_w and last_h tracking detects window or game resolution changes that require a recreation. Without it, a resized surface continues to use its old dimensions until the next time surface_exists returns false — which may not happen for many frames.

Step 2: Render to the surface in the same Draw event, not later. After ensuring the surface exists and is cleared, draw the actual content immediately. Do not defer the draw to a later event — if anything else triggers a recreation between Draw GUI Begin and Draw GUI, you will be drawing into a different surface than the one you just cleared.

// obj_minimap Draw GUI event — same instance, same frame
if (!surface_exists(minimap_surf)) {
    // Defensive: should have been created in Begin, but check anyway
    show_debug_message("Minimap surface lost mid-frame");
    exit;
}

surface_set_target(minimap_surf);
draw_clear_alpha(c_black, 0);

// Render every world entity to the minimap
with (obj_world_entity) {
    var _mx = (x / room_width) * 256;
    var _my = (y / room_height) * 256;
    draw_circle_color(_mx, _my, 2, minimap_color, minimap_color, false);
}

surface_reset_target();

// Now blit the surface to the screen
draw_surface(minimap_surf, 10, 10);

Notice the redundant draw_clear_alpha at the start of the Draw GUI event. Even though Draw GUI Begin cleared the surface on creation, every frame should clear before redrawing if the content is regenerated. If the content is incremental (you draw on top of the previous frame), skip the clear — but accept that any garbage from before the first clear will persist forever.

Application Suspend Handling

On mobile and on some Windows configurations, surfaces are dropped when the application is backgrounded. The surface_exists check catches this on resume because the surface no longer exists in the engine’s table. The recreation path then runs and starts fresh.

If your surface contains content that cannot be regenerated cheaply (a player drawing canvas, a procedurally generated map), back the surface to a buffer or sprite before suspend. Use the System Event » iOS / Android Suspend event (or detect os_get_info changes on desktop) to call surface_save or surface_copy to a sprite atlas. On resume, recreate the surface and copy the saved data back.

Performance Note

surface_set_target followed by draw_clear_alpha is fast on every modern GPU — it issues a single fullscreen rect clear. There is no reason to skip it for performance. The cost of a stale-surface bug in production is far higher than the microsecond cost of the clear.

The one exception is if you are doing per-pixel accumulation where the previous frame’s content matters (motion blur, persistence-of-vision effects). For those, set up a double-buffer pattern: two surfaces, alternate between them each frame, only clear when starting a new accumulation cycle.

“A surface is a window into VRAM. The window is yours, but the VRAM behind it is whatever was there before. Always wipe the glass before you look through it.”

Related Issues

If your surface draws correctly but at the wrong scale on high-DPI displays, see Surface DPI Scaling Mismatch for application surface configuration. If surfaces flicker on the very first frame of every room, check Surface Flicker on Room Start for early-create patterns.

surface_exists is a five-character function that prevents most surface bugs — use it everywhere.