Skip to content

Resource Management

Resource management is essential for efficient and stable rendering in Minecraft 26.1. This guide covers working with GPU resources, memory management, and performance optimization patterns.

The resource management system provides:

  • Abstracted GPU resource interfaces through GpuBuffer and GpuTexture
  • Automatic resource lifecycle with proper cleanup
  • Buffer pooling for memory efficiency
  • Thread-safe operations for multi-threaded rendering
  • Memory leak prevention through RAII patterns

The GpuBuffer interface provides platform-agnostic buffer operations:

com.mojang.blaze3d.buffers.GpuBuffer
public interface GpuBuffer extends AutoCloseable {
enum BufferType {
VERTEX, // Vertex data buffer
INDEX, // Index data buffer
UNIFORM, // Uniform data buffer
STORAGE // Storage buffer (if supported)
}
enum UsageFlag {
MAP_READ(1L), // Buffer can be read by CPU
MAP_WRITE(2L), // Buffer can be written by CPU
COPY_SRC(4L), // Buffer can be copy source
COPY_DST(8L), // Buffer can be copy destination
VERTEX(16L), // Buffer can be vertex binding
INDEX(32L), // Buffer can be index binding
UNIFORM(64L); // Buffer can be uniform binding
public final long mask;
UsageFlag(long mask) { this.mask = mask; }
}
// Core buffer operations
void bind(int binding);
void unbind();
void upload(ByteBuffer data);
void upload(long offset, ByteBuffer data);
GpuBufferSlice slice(long offset, long length);
ByteBuffer map(GpuBuffer.MapAccess access);
void unmap();
long getSize();
void close();
}
// Example: Basic buffer usage
public class BufferExample {
private GpuBuffer vertexBuffer;
private final GpuDevice device;
public BufferExample() {
this.device = RenderSystem.getDevice();
}
public void createVertexBuffer(float[] vertices) {
RenderSystem.assertOnRenderThread();
// Create buffer with appropriate usage flags
this.vertexBuffer = device.createBuffer(
GpuBuffer.BufferType.VERTEX,
GpuBuffer.Usage.STATIC_DRAW | GpuBuffer.Usage.MAP_WRITE
);
// Convert float array to byte buffer
ByteBuffer byteBuffer = BufferUtils.createFloatBuffer(vertices.length);
byteBuffer.asFloatBuffer().put(vertices).flip();
// Upload data to GPU
vertexBuffer.upload(byteBuffer);
}
public void renderBuffer() {
RenderSystem.assertOnRenderThread();
// Bind buffer for drawing
vertexBuffer.bind();
// Issue draw call
int vertexCount = (int)(vertexBuffer.getSize() / (6 * Float.BYTES)); // 6 floats per vertex
GL11.glDrawArrays(GL11.GL_QUADS, 0, vertexCount);
// Unbind buffer
vertexBuffer.unbind();
}
public void cleanup() {
if (vertexBuffer != null) {
vertexBuffer.close();
}
}
}
// Example: Using BufferBuilder for dynamic geometry
public class DynamicGeometryExample {
private final Tesselator tesselator;
private final BufferBuilder builder;
public DynamicGeometryExample() {
this.tesselator = Tesselator.getInstance();
this.builder = tesselator.getBuilder();
}
public void renderColoredCube(PoseStack poseStack, float x, float y, float z,
Color color) {
RenderSystem.assertOnRenderThread();
poseStack.pushPose();
poseStack.translate(x, y, z);
Matrix4f matrix = poseStack.last().pose();
// Begin building geometry
builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR);
// Build a colored cube
// Front face
builder.addVertex(matrix, -0.5f, -0.5f, 0.5f)
.setColor(color.getRed(), color.getGreen(), color.getBlue(), 255)
.endVertex();
builder.addVertex(matrix, 0.5f, -0.5f, 0.5f)
.setColor(color.getRed(), color.getGreen(), color.getBlue(), 255)
.endVertex();
builder.addVertex(matrix, 0.5f, 0.5f, 0.5f)
.setColor(color.getRed(), color.getGreen(), color.getBlue(), 255)
.endVertex();
builder.addVertex(matrix, -0.5f, 0.5f, 0.5f)
.setColor(color.getRed(), color.getGreen(), color.getBlue(), 255)
.endVertex();
// Add remaining faces...
// End building and draw
BufferBuilder.RenderedBuffer buffer = builder.end();
BufferUploader.draw(buffer);
poseStack.popPose();
}
}
com.mojang.blaze3d.textures.GpuTexture
public interface GpuTexture extends AutoCloseable {
enum TextureType {
TEXTURE_1D, // 1D texture
TEXTURE_2D, // 2D texture (most common)
TEXTURE_3D, // 3D texture
TEXTURE_CUBE_MAP, // Cube map texture
TEXTURE_1D_ARRAY, // 1D texture array
TEXTURE_2D_ARRAY, // 2D texture array
TEXTURE_CUBE_MAP_ARRAY // Cube map array
}
enum UsageFlag {
COPY_SRC(1L), // Texture can be copy source
COPY_DST(2L), // Texture can be copy destination
TEXTURE_BINDING(4L), // Texture can be bound for sampling
RENDER_ATTACHMENT(8L), // Texture can be render target
CUBEMAP_COMPATIBLE(16L); // Texture can be used as cube map
public final long mask;
UsageFlag(long mask) { this.mask = mask; }
}
// Core texture operations
void upload(int level, int x, int y, int width, int height,
TextureFormat format, ByteBuffer buffer);
GpuTextureView createView(int level, int layer);
void bind(int unit);
void unbind();
void generateMipmaps();
int getWidth();
int getHeight();
void close();
}
// Example: Basic texture management
public class TextureExample {
private final Map<String, GpuTexture> textures = new HashMap<>();
private final GpuDevice device;
public TextureExample() {
this.device = RenderSystem.getDevice();
}
public void loadTexture(String name, BufferedImage image) {
RenderSystem.assertOnRenderThread();
// Convert image to byte buffer
int[] pixels = new int[image.getWidth() * image.getHeight()];
image.getRGB(0, 0, image.getWidth(), image.getHeight(), pixels, 0, image.getWidth());
ByteBuffer buffer = BufferUtils.createIntBuffer(pixels.length);
buffer.asIntBuffer().put(pixels).flip();
// Create texture
GpuTexture texture = device.createTexture(
GpuTexture.TextureType.TEXTURE_2D,
image.getWidth(),
image.getHeight(),
GpuTexture.Usage.TEXTURE_BINDING | GpuTexture.Usage.COPY_SRC
);
// Upload image data
texture.upload(0, 0, 0, image.getWidth(), image.getHeight(),
TextureFormat.RGBA, buffer);
// Generate mipmaps for better quality
texture.generateMipmaps();
this.textures.put(name, texture);
}
public void bindTexture(String name, int unit) {
GpuTexture texture = textures.get(name);
if (texture != null) {
texture.bind(unit);
}
}
public void cleanup() {
for (GpuTexture texture : textures.values()) {
texture.close();
}
textures.clear();
}
}
// Example: Buffer pool for reducing allocations
public class BufferPool {
private final Queue<GpuBuffer> availableBuffers = new ArrayDeque<>();
private final GpuDevice device;
private final int bufferSize;
private final GpuBuffer.BufferType bufferType;
public BufferPool(GpuDevice device, int bufferSize, GpuBuffer.BufferType bufferType) {
this.device = device;
this.bufferSize = bufferSize;
this.bufferType = bufferType;
}
public GpuBuffer acquire() {
RenderSystem.assertOnRenderThread();
GpuBuffer buffer = availableBuffers.poll();
if (buffer == null) {
buffer = device.createBuffer(bufferType, GpuBuffer.Usage.DYNAMIC_DRAW);
}
return buffer;
}
public void release(GpuBuffer buffer) {
RenderSystem.assertOnRenderThread();
if (availableBuffers.size() < 16) { // Limit pool size
availableBuffers.offer(buffer);
} else {
buffer.close();
}
}
public void cleanup() {
RenderSystem.assertOnRenderThread();
for (GpuBuffer buffer : availableBuffers) {
buffer.close();
}
availableBuffers.clear();
}
}
// Example: Texture caching system
public class TextureCache {
private static final Map<ResourceLocation, GpuTexture> textureCache = new ConcurrentHashMap<>();
private static final GpuDevice device = RenderSystem.getDevice();
public static GpuTexture getTexture(ResourceLocation location) {
return textureCache.computeIfAbsent(location, TextureCache::loadTexture);
}
private static GpuTexture loadTexture(ResourceLocation location) {
RenderSystem.assertOnRenderThread();
try {
// Load texture from resource manager
NativeImage image = NativeImage.read(Minecraft.getInstance()
.getResourceManager().getResource(location).getInputStream());
// Create GPU texture
GpuTexture texture = device.createTexture(
GpuTexture.TextureType.TEXTURE_2D,
image.getWidth(),
image.getHeight(),
GpuTexture.Usage.TEXTURE_BINDING
);
// Upload image data
texture.upload(0, 0, 0, image.getWidth(), image.getHeight(),
TextureFormat.RGBA, image.getPixels());
// Generate mipmaps
texture.generateMipmaps();
return texture;
} catch (IOException e) {
throw new RuntimeException("Failed to load texture: " + location, e);
}
}
public static void cleanup() {
RenderSystem.assertOnRenderThread();
for (GpuTexture texture : textureCache.values()) {
texture.close();
}
textureCache.clear();
}
}
// Example: RAII pattern for automatic resource cleanup
public class ScopedResource implements AutoCloseable {
private final GpuBuffer buffer;
private final GpuTexture texture;
private final GpuSampler sampler;
public ScopedResource() {
RenderSystem.assertOnRenderThread();
GpuDevice device = RenderSystem.getDevice();
this.buffer = device.createBuffer(
GpuBuffer.BufferType.VERTEX,
GpuBuffer.Usage.STATIC_DRAW);
this.texture = device.createTexture(
GpuTexture.TextureType.TEXTURE_2D, 256, 256,
GpuTexture.Usage.TEXTURE_BINDING);
this.sampler = device.createSampler(
GpuSampler.WrapMode.REPEAT,
GpuSampler.WrapMode.REPEAT,
GpuSampler.MinFilter.LINEAR,
GpuSampler.MagFilter.LINEAR
);
}
public GpuBuffer getBuffer() { return buffer; }
public GpuTexture getTexture() { return texture; }
public GpuSampler getSampler() { return sampler; }
@Override
public void close() {
RenderSystem.assertOnRenderThread();
if (buffer != null) buffer.close();
if (texture != null) texture.close();
if (sampler != null) sampler.close();
}
}
// Usage with try-with-resources
public void renderWithScopedResources() {
RenderSystem.assertOnRenderThread();
try (ScopedResource resources = new ScopedResource()) {
// Use resources
resources.getTexture().bind(0);
resources.getBuffer().bind();
// Perform rendering
renderGeometry();
}
// Resources automatically cleaned up
}
// Example: Lifecycle-aware resource manager
public class ResourceManager implements Disposable {
private final List<AutoCloseable> resources = new ArrayList<>();
private boolean disposed = false;
public <T extends AutoCloseable> T addResource(T resource) {
if (!disposed) {
resources.add(resource);
}
return resource;
}
public GpuBuffer createBuffer(GpuBuffer.BufferType type, long usage) {
RenderSystem.assertOnRenderThread();
GpuBuffer buffer = RenderSystem.getDevice().createBuffer(type, usage);
return addResource(buffer);
}
public GpuTexture createTexture(GpuTexture.TextureType type, int width, int height, long usage) {
RenderSystem.assertOnRenderThread();
GpuTexture texture = RenderSystem.getDevice().createTexture(type, width, height, usage);
return addResource(texture);
}
@Override
public void dispose() {
if (!disposed) {
disposed = true;
// Dispose resources in reverse order of creation
for (int i = resources.size() - 1; i >= 0; i--) {
try {
resources.get(i).close();
} catch (Exception e) {
// Log error but continue cleanup
}
}
resources.clear();
}
}
}
// Example: Batched resource operations
public class BatchedResourceUploader {
private final List<UploadOperation> pendingOperations = new ArrayList<>();
private static class UploadOperation {
final GpuBuffer buffer;
final ByteBuffer data;
final long offset;
UploadOperation(GpuBuffer buffer, ByteBuffer data, long offset) {
this.buffer = buffer;
this.data = data;
this.offset = offset;
}
}
public void scheduleUpload(GpuBuffer buffer, ByteBuffer data, long offset) {
pendingOperations.add(new UploadOperation(buffer, data, offset));
}
public void executePendingUploads() {
RenderSystem.assertOnRenderThread();
// Sort operations by resource to minimize state changes
pendingOperations.sort((a, b) ->
Long.compare(System.identityHashCode(a.buffer), System.identityHashCode(b.buffer)));
GpuBuffer currentBuffer = null;
for (UploadOperation operation : pendingOperations) {
if (operation.buffer != currentBuffer) {
// Bind new buffer only when necessary
if (currentBuffer != null) {
currentBuffer.unbind();
}
operation.buffer.bind();
currentBuffer = operation.buffer;
}
// Upload data
operation.buffer.upload(operation.offset, operation.data);
}
// Unbind final buffer
if (currentBuffer != null) {
currentBuffer.unbind();
}
pendingOperations.clear();
}
}
// Example: Memory monitoring for resource usage
public class ResourceMonitor {
private static long totalBufferMemory = 0;
private static long totalTextureMemory = 0;
private static final Map<GpuBuffer, Long> bufferSizes = new WeakHashMap<>();
private static final Map<GpuTexture, Long> textureSizes = new WeakHashMap<>();
public static void trackBuffer(GpuBuffer buffer) {
long size = buffer.getSize();
bufferSizes.put(buffer, size);
totalBufferMemory += size;
}
public static void trackTexture(GpuTexture texture) {
long size = (long) texture.getWidth() * texture.getHeight() * 4; // RGBA
textureSizes.put(texture, size);
totalTextureMemory += size;
}
public static void releaseBuffer(GpuBuffer buffer) {
Long size = bufferSizes.remove(buffer);
if (size != null) {
totalBufferMemory -= size;
}
}
public static void releaseTexture(GpuTexture texture) {
Long size = textureSizes.remove(texture);
if (size != null) {
totalTextureMemory -= size;
}
}
public static String getMemoryReport() {
return String.format("Buffer Memory: %d MB, Texture Memory: %d MB, Total: %d MB",
totalBufferMemory / (1024 * 1024),
totalTextureMemory / (1024 * 1024),
(totalBufferMemory + totalTextureMemory) / (1024 * 1024));
}
}

Problem: Memory usage increases over time Solution: Use RAII patterns and proper cleanup

// ❌ WRONG - Not cleaned up
public void renderWithLeak() {
GpuBuffer buffer = RenderSystem.getDevice().createBuffer(...);
buffer.upload(data);
// Buffer never closed - memory leak!
}
// ✅ CORRECT - Proper cleanup
public void renderWithoutLeak() {
try (GpuBuffer buffer = RenderSystem.getDevice().createBuffer(...)) {
buffer.upload(data);
// Use buffer...
}
// Buffer automatically closed
}

Problem: Crashes due to non-render thread operations Solution: Always check thread safety

// ❌ WRONG - Not on render thread
public void createResourceFromBackground() {
GpuTexture texture = RenderSystem.getDevice().createTexture(...); // Crashes!
}
// ✅ CORRECT - On render thread
public void createResourceSafely(Runnable resourceCreation) {
if (RenderSystem.isOnRenderThread()) {
resourceCreation.run();
} else {
Minecraft.getInstance().execute(resourceCreation);
}
}

Problem: Poor performance due to frequent buffer creation Solution: Use pooling and caching

// ❌ WRONG - New buffer every frame
public void renderEveryFrame() {
GpuBuffer buffer = RenderSystem.getDevice().createBuffer(...);
// Upload and render...
buffer.close(); // Inefficient!
}
// ✅ CORRECT - Reuse buffer
public class EfficientRenderer {
private GpuBuffer buffer;
public void renderEveryFrame() {
if (buffer == null) {
buffer = RenderSystem.getDevice().createBuffer(...);
}
// Upload and render...
}
}
  1. Always use RAII patterns for automatic resource cleanup
  2. Pool frequently used resources to reduce allocations
  3. Monitor memory usage to detect leaks early
  4. Check thread safety before resource operations
  5. Batch operations to minimize state changes
  6. Use appropriate usage flags for optimal GPU performance
  7. Implement proper disposal for resource managers