Resource Management
Resource Management
Section titled “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.
Overview
Section titled “Overview”The resource management system provides:
- Abstracted GPU resource interfaces through
GpuBufferandGpuTexture - Automatic resource lifecycle with proper cleanup
- Buffer pooling for memory efficiency
- Thread-safe operations for multi-threaded rendering
- Memory leak prevention through RAII patterns
Buffer Management
Section titled “Buffer Management”GpuBuffer Interface
Section titled “GpuBuffer Interface”The GpuBuffer interface provides platform-agnostic buffer operations:
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();}Creating and Using Buffers
Section titled “Creating and Using Buffers”// Example: Basic buffer usagepublic 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(); } }}BufferBuilder for Dynamic Geometry
Section titled “BufferBuilder for Dynamic Geometry”// Example: Using BufferBuilder for dynamic geometrypublic 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(); }}Texture Management
Section titled “Texture Management”GpuTexture Interface
Section titled “GpuTexture Interface”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();}Creating and Using Textures
Section titled “Creating and Using Textures”// Example: Basic texture managementpublic 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(); }}Resource Pools and Caching
Section titled “Resource Pools and Caching”Buffer Pooling
Section titled “Buffer Pooling”// Example: Buffer pool for reducing allocationspublic 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(); }}Texture Caching
Section titled “Texture Caching”// Example: Texture caching systempublic 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(); }}Memory Management Patterns
Section titled “Memory Management Patterns”RAII Pattern
Section titled “RAII Pattern”// Example: RAII pattern for automatic resource cleanuppublic 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-resourcespublic 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}Resource Lifecycle Management
Section titled “Resource Lifecycle Management”// Example: Lifecycle-aware resource managerpublic 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(); } }}Performance Optimization
Section titled “Performance Optimization”Batch Resource Operations
Section titled “Batch Resource Operations”// Example: Batched resource operationspublic 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(); }}Memory Monitoring
Section titled “Memory Monitoring”// Example: Memory monitoring for resource usagepublic 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)); }}Common Issues and Solutions
Section titled “Common Issues and Solutions”Resource Leaks
Section titled “Resource Leaks”Problem: Memory usage increases over time Solution: Use RAII patterns and proper cleanup
// ❌ WRONG - Not cleaned uppublic void renderWithLeak() { GpuBuffer buffer = RenderSystem.getDevice().createBuffer(...); buffer.upload(data); // Buffer never closed - memory leak!}
// ✅ CORRECT - Proper cleanuppublic void renderWithoutLeak() { try (GpuBuffer buffer = RenderSystem.getDevice().createBuffer(...)) { buffer.upload(data); // Use buffer... } // Buffer automatically closed}Thread Safety Violations
Section titled “Thread Safety Violations”Problem: Crashes due to non-render thread operations Solution: Always check thread safety
// ❌ WRONG - Not on render threadpublic void createResourceFromBackground() { GpuTexture texture = RenderSystem.getDevice().createTexture(...); // Crashes!}
// ✅ CORRECT - On render threadpublic void createResourceSafely(Runnable resourceCreation) { if (RenderSystem.isOnRenderThread()) { resourceCreation.run(); } else { Minecraft.getInstance().execute(resourceCreation); }}Excessive Allocations
Section titled “Excessive Allocations”Problem: Poor performance due to frequent buffer creation Solution: Use pooling and caching
// ❌ WRONG - New buffer every framepublic void renderEveryFrame() { GpuBuffer buffer = RenderSystem.getDevice().createBuffer(...); // Upload and render... buffer.close(); // Inefficient!}
// ✅ CORRECT - Reuse bufferpublic class EfficientRenderer { private GpuBuffer buffer;
public void renderEveryFrame() { if (buffer == null) { buffer = RenderSystem.getDevice().createBuffer(...); } // Upload and render... }}Best Practices
Section titled “Best Practices”- Always use RAII patterns for automatic resource cleanup
- Pool frequently used resources to reduce allocations
- Monitor memory usage to detect leaks early
- Check thread safety before resource operations
- Batch operations to minimize state changes
- Use appropriate usage flags for optimal GPU performance
- Implement proper disposal for resource managers
Next Steps
Section titled “Next Steps”- Render Types - Using resources with render types
- Performance Optimization - Advanced optimization techniques
- Memory Management - Deep dive into memory patterns
- Complete Examples - Working examples with proper resource management