Mapped buffer sanity

- MappedBuffer no longer abstract
 - Mappable interface used within MappedBuffer
 - MappedBuffer is mapped before ctor
 - No more subclasses, no more thin wrapping overrides
 - Use try with resources for buffer mapping
 - Better error handling with mapped buffers
 - Unimplemented exception as a procrastination method
This commit is contained in:
Jozufozu 2021-12-10 14:45:18 -08:00
parent fd8d436640
commit 21a23d650f
13 changed files with 162 additions and 280 deletions

View file

@ -0,0 +1,14 @@
package com.jozufozu.flywheel.backend.gl.buffer;
/**
* Interface for generically dealing with mapped buffers.
*/
public interface Mappable {
GlBufferType getType();
/**
* Indicates that this buffer need not be #flush()'d for its contents to sync.
* @return true if this buffer is persistently mapped.
*/
boolean isPersistent();
}

View file

@ -1,133 +1,45 @@
package com.jozufozu.flywheel.backend.gl.buffer;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import org.lwjgl.opengl.GL15;
import org.lwjgl.system.MemoryUtil;
public abstract class MappedBuffer extends VecBuffer implements AutoCloseable {
public class MappedBuffer extends VecBuffer implements AutoCloseable {
protected boolean mapped;
protected final GlBuffer owner;
protected final long offset;
protected final long length;
protected final Mappable owner;
public MappedBuffer(GlBuffer owner) {
public MappedBuffer(Mappable owner, ByteBuffer internal, long offset, long length) {
this.internal = internal;
this.owner = owner;
this.offset = offset;
this.length = length;
}
public long addr() {
return MemoryUtil.memAddress(this.internal, internal.position());
}
protected abstract void checkAndMap();
/**
* Make the changes in client memory available to the GPU.
*/
public void flush() {
if (mapped) {
GL15.glUnmapBuffer(owner.type.glEnum);
mapped = false;
setInternal(null);
if (owner.isPersistent()) return;
if (internal == null) return;
GL15.glUnmapBuffer(owner.getType().glEnum);
internal = null;
}
@Override
public MappedBuffer position(int p) {
if (p < offset || p >= offset + length) {
throw new IndexOutOfBoundsException("Index " + p + " is not mapped");
}
super.position(p - (int) offset);
return this;
}
@Override
public void close() throws Exception {
flush();
}
public MappedBuffer putFloatArray(float[] floats) {
checkAndMap();
super.putFloatArray(floats);
return this;
}
public MappedBuffer putByteArray(byte[] bytes) {
checkAndMap();
super.putByteArray(bytes);
return this;
}
public MappedBuffer put(FloatBuffer floats) {
checkAndMap();
super.put(floats);
return this;
}
public int position() {
checkAndMap();
return super.position();
}
/**
* Position this buffer relative to the 0-index in GPU memory.
*
* @return This buffer.
*/
public MappedBuffer position(int p) {
checkAndMap();
super.position(p);
return this;
}
public MappedBuffer putFloat(float f) {
checkAndMap();
super.putFloat(f);
return this;
}
public MappedBuffer putInt(int i) {
checkAndMap();
super.putInt(i);
return this;
}
public MappedBuffer putShort(short s) {
checkAndMap();
super.putShort(s);
return this;
}
public MappedBuffer put(byte b) {
checkAndMap();
super.put(b);
return this;
}
public MappedBuffer put(ByteBuffer b) {
checkAndMap();
super.put(b);
return this;
}
public MappedBuffer putVec4(float x, float y, float z, float w) {
checkAndMap();
super.putVec4(x, y, z, w);
return this;
}
public MappedBuffer putVec3(float x, float y, float z) {
checkAndMap();
super.putVec3(x, y, z);
return this;
}
public MappedBuffer putVec2(float x, float y) {
checkAndMap();
super.putVec2(x, y);
return this;
}
public MappedBuffer putVec3(byte x, byte y, byte z) {
checkAndMap();
super.putVec3(x, y, z);
return this;
}
public MappedBuffer putVec2(byte x, byte y) {
checkAndMap();
super.putVec2(x, y);
return this;
}
}

View file

@ -1,40 +0,0 @@
package com.jozufozu.flywheel.backend.gl.buffer;
import org.lwjgl.opengl.GL30;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.gl.error.GlError;
import com.jozufozu.flywheel.backend.gl.error.GlException;
import com.jozufozu.flywheel.util.StringUtil;
import com.mojang.blaze3d.platform.GlStateManager;
public class MappedBufferRange extends MappedBuffer {
long offset, length;
int access;
public MappedBufferRange(GlBuffer buffer, long offset, long length, int access) {
super(buffer);
this.offset = offset;
this.length = length;
this.access = access;
}
@Override
public MappedBuffer position(int p) {
if (p < offset || p >= offset + length) {
throw new IndexOutOfBoundsException("Index " + p + " is not mapped");
}
return super.position(p - (int) offset);
}
@Override
protected void checkAndMap() {
if (!mapped) {
setInternal(GL30.glMapBufferRange(owner.type.glEnum, offset, length, access));
GlError.pollAndThrow(() -> StringUtil.args("mapBufferRange", owner.type, offset, length, access));
mapped = true;
}
}
}

View file

@ -5,7 +5,11 @@ import java.nio.ByteBuffer;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL30;
public class MappedGlBuffer extends GlBuffer {
import com.jozufozu.flywheel.backend.gl.error.GlError;
import com.jozufozu.flywheel.backend.gl.error.GlException;
import com.jozufozu.flywheel.util.StringUtil;
public class MappedGlBuffer extends GlBuffer implements Mappable {
protected final GlBufferUsage usage;
@ -27,6 +31,22 @@ public class MappedGlBuffer extends GlBuffer {
}
public MappedBuffer getBuffer(int offset, int length) {
return new MappedBufferRange(this, offset, length, GL30.GL_MAP_WRITE_BIT);
ByteBuffer byteBuffer = GL30.glMapBufferRange(type.glEnum, offset, length, GL30.GL_MAP_WRITE_BIT);
if (byteBuffer == null) {
throw new GlException(GlError.poll(), "Could not map buffer");
}
return new MappedBuffer(this, byteBuffer, offset, length);
}
@Override
public GlBufferType getType() {
return type;
}
@Override
public boolean isPersistent() {
return false;
}
}

View file

@ -6,16 +6,19 @@ import static org.lwjgl.opengl.GL44.GL_MAP_PERSISTENT_BIT;
import java.nio.ByteBuffer;
import org.lwjgl.opengl.GL30;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.gl.GlFence;
import com.jozufozu.flywheel.backend.gl.error.GlError;
import com.jozufozu.flywheel.backend.gl.error.GlException;
import com.jozufozu.flywheel.util.StringUtil;
import com.jozufozu.flywheel.util.Unimplemented;
public class PersistentGlBuffer extends GlBuffer {
public class PersistentGlBuffer extends GlBuffer implements Mappable {
private PersistentMappedBuffer buffer;
private MappedBuffer buffer;
int flags;
long size;
@ -48,14 +51,25 @@ public class PersistentGlBuffer extends GlBuffer {
Backend.getInstance().compat.bufferStorage.bufferStorage(type, size, flags);
GlError.pollAndThrow(() -> StringUtil.args("bufferStorage", type, size, flags));
GlError error = GlError.poll();
if (error != null) {
// If this error is being thrown but everything seems fine,
// GlError.poll() might be returning an error from something earlier.
throw new GlException(error, StringUtil.args("bufferStorage", type, size, flags));
}
buffer = new PersistentMappedBuffer(this);
ByteBuffer byteBuffer = GL30.glMapBufferRange(type.glEnum, 0, size, flags);
if (byteBuffer == null) {
throw new GlException(GlError.poll(), "Could not map buffer");
}
buffer = new MappedBuffer(this, byteBuffer, 0, size);
}
@Override
public void upload(ByteBuffer directBuffer) {
throw new Unimplemented("FIXME: Nothing calls #upload on a persistent buffer as of 12/10/2021.");
}
@Override
@ -67,4 +81,14 @@ public class PersistentGlBuffer extends GlBuffer {
return buffer;
}
@Override
public GlBufferType getType() {
return type;
}
@Override
public boolean isPersistent() {
return true;
}
}

View file

@ -1,48 +0,0 @@
package com.jozufozu.flywheel.backend.gl.buffer;
import java.nio.ByteBuffer;
import org.lwjgl.opengl.GL30;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.gl.error.GlError;
import com.jozufozu.flywheel.backend.gl.error.GlException;
import com.jozufozu.flywheel.util.StringUtil;
public class PersistentMappedBuffer extends MappedBuffer {
private final long offset;
private final long length;
PersistentGlBuffer owner;
public PersistentMappedBuffer(PersistentGlBuffer buffer) {
super(buffer);
owner = buffer;
offset = 0;
length = owner.size;
ByteBuffer byteBuffer = GL30.glMapBufferRange(owner.type.glEnum, offset, length, owner.flags);
GlError.pollAndThrow(() -> StringUtil.args("mapBuffer", owner.type, offset, length, owner.flags));
setInternal(byteBuffer);
}
@Override
public MappedBuffer position(int p) {
if (p < offset || p >= offset + length) {
throw new IndexOutOfBoundsException("Index " + p + " is not mapped");
}
return super.position(p - (int) offset);
}
@Override
public void flush() {
}
@Override
protected void checkAndMap() {
}
}

View file

@ -24,10 +24,6 @@ public class VecBuffer {
return new VecBuffer(buffer);
}
protected void setInternal(@Nullable ByteBuffer internal) {
this.internal = internal;
}
public ByteBuffer unwrap() {
return internal;
}

View file

@ -4,35 +4,12 @@ public class GlException extends RuntimeException {
final GlError errorCode;
@Override
public String toString() {
String s = getClass().getName();
String message = getLocalizedMessage();
String withCode = s + ": " + errorCode;
return (message != null) ? (withCode + ": " + message) : withCode;
}
public GlException(GlError errorCode) {
this.errorCode = errorCode;
}
public GlException(GlError errorCode, String message) {
super(message);
super(updateMessage(errorCode, message));
this.errorCode = errorCode;
}
public GlException(GlError errorCode, String message, Throwable cause) {
super(message, cause);
this.errorCode = errorCode;
}
public GlException(GlError errorCode, Throwable cause) {
super(cause);
this.errorCode = errorCode;
}
public GlException(GlError errorCode, String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
this.errorCode = errorCode;
private static String updateMessage(GlError error, String message) {
return String.format("%s: %s", error, message);
}
}

View file

@ -2,6 +2,7 @@ package com.jozufozu.flywheel.backend.instancing.instancing;
import java.util.BitSet;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.gl.GlVertexArray;
import com.jozufozu.flywheel.backend.gl.attrib.VertexFormat;
@ -136,9 +137,11 @@ public class GPUInstancer<D extends InstanceData> extends AbstractInstancer<D> {
final int offset = size * instanceFormat.getStride();
final int length = glBufferSize - offset;
if (length > 0) {
instanceVBO.getBuffer(offset, length)
.putByteArray(new byte[length])
.flush();
try (MappedBuffer buf = instanceVBO.getBuffer(offset, length)) {
buf.putByteArray(new byte[length]);
} catch (Exception e) {
Flywheel.log.error("Error clearing buffer tail:", e);
}
}
}
@ -159,16 +162,19 @@ public class GPUInstancer<D extends InstanceData> extends AbstractInstancer<D> {
final int length = (1 + lastDirty - firstDirty) * stride;
if (length > 0) {
MappedBuffer mapped = instanceVBO.getBuffer(offset, length);
try (MappedBuffer mapped = instanceVBO.getBuffer(offset, length)) {
StructWriter<D> writer = type.asInstanced().getWriter(mapped);
StructWriter<D> writer = type.asInstanced()
.getWriter(mapped);
dirtySet.stream()
.forEach(i -> {
writer.seek(i);
writer.write(data.get(i));
});
mapped.flush();
dirtySet.stream()
.forEach(i -> {
writer.seek(i);
writer.write(data.get(i));
});
} catch (Exception e) {
Flywheel.log.error("Error updating GPUInstancer:", e);
}
}
}
@ -180,12 +186,15 @@ public class GPUInstancer<D extends InstanceData> extends AbstractInstancer<D> {
glBufferSize = requiredSize + stride * 16;
instanceVBO.alloc(glBufferSize);
MappedBuffer buffer = instanceVBO.getBuffer(0, glBufferSize);
StructWriter<D> writer = type.asInstanced().getWriter(buffer);
for (D datum : data) {
writer.write(datum);
try (MappedBuffer buffer = instanceVBO.getBuffer(0, glBufferSize)) {
StructWriter<D> writer = type.asInstanced()
.getWriter(buffer);
for (D datum : data) {
writer.write(datum);
}
} catch (Exception e) {
Flywheel.log.error("Error reallocating GPUInstancer:", e);
}
buffer.flush();
glInstanceCount = size;

View file

@ -4,6 +4,7 @@ import static org.lwjgl.opengl.GL11.glDrawArrays;
import org.lwjgl.opengl.GL31;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.backend.gl.GlPrimitive;
import com.jozufozu.flywheel.backend.gl.attrib.VertexFormat;
import com.jozufozu.flywheel.backend.gl.buffer.GlBuffer;
@ -32,9 +33,11 @@ public class BufferedModel implements IBufferedModel {
vbo.alloc(model.size());
// mirror it in system memory so we can write to it, and upload our model.
MappedBuffer buffer = vbo.getBuffer(0, model.size());
model.buffer(new VecBufferWriter(buffer));
buffer.flush();
try (MappedBuffer buffer = vbo.getBuffer(0, model.size())) {
model.buffer(new VecBufferWriter(buffer));
} catch (Exception e) {
Flywheel.log.error(String.format("Error uploading model '%s':", model.name()), e);
}
vbo.unbind();
}

View file

@ -5,6 +5,7 @@ import java.util.List;
import org.lwjgl.opengl.GL32;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.backend.gl.GlPrimitive;
import com.jozufozu.flywheel.backend.gl.attrib.VertexFormat;
import com.jozufozu.flywheel.backend.gl.buffer.GlBuffer;
@ -115,34 +116,35 @@ public class ModelPool implements ModelAllocator {
}
private void uploadAll() {
MappedBuffer buffer = vbo.getBuffer(0, bufferSize);
try (MappedBuffer buffer = vbo.getBuffer(0, bufferSize)) {
VecBufferWriter consumer = new VecBufferWriter(buffer);
VecBufferWriter consumer = new VecBufferWriter(buffer);
for (PooledModel model : models) {
model.model.buffer(consumer);
if (model.callback != null)
model.callback.onAlloc(model);
for (PooledModel model : models) {
model.model.buffer(consumer);
if (model.callback != null) model.callback.onAlloc(model);
}
} catch (Exception e) {
Flywheel.log.error("Error uploading pooled models:", e);
}
buffer.flush();
}
private void uploadPending() {
MappedBuffer buffer = vbo.getBuffer(0, bufferSize);
VecBufferWriter consumer = new VecBufferWriter(buffer);
try (MappedBuffer buffer = vbo.getBuffer(0, bufferSize)) {
VecBufferWriter consumer = new VecBufferWriter(buffer);
int stride = format.getStride();
for (PooledModel model : pendingUpload) {
int pos = model.first * stride;
buffer.position(pos);
model.model.buffer(consumer);
if (model.callback != null)
model.callback.onAlloc(model);
int stride = format.getStride();
for (PooledModel model : pendingUpload) {
int pos = model.first * stride;
buffer.position(pos);
model.model.buffer(consumer);
if (model.callback != null) model.callback.onAlloc(model);
}
pendingUpload.clear();
} catch (Exception e) {
Flywheel.log.error("Error uploading pooled models:", e);
}
pendingUpload.clear();
buffer.flush();
}
private void setDirty() {

View file

@ -1,11 +1,15 @@
package com.jozufozu.flywheel.core;
import static org.lwjgl.opengl.GL20.*;
import org.lwjgl.opengl.GL20;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.backend.gl.GlNumericType;
import com.jozufozu.flywheel.backend.gl.GlVertexArray;
import com.jozufozu.flywheel.backend.gl.buffer.GlBuffer;
import com.jozufozu.flywheel.backend.gl.buffer.GlBufferType;
import com.jozufozu.flywheel.backend.gl.buffer.MappedBuffer;
import com.jozufozu.flywheel.backend.gl.buffer.MappedGlBuffer;
import com.jozufozu.flywheel.util.Lazy;
@ -28,16 +32,18 @@ public class FullscreenQuad {
vbo = new MappedGlBuffer(GlBufferType.ARRAY_BUFFER);
vbo.bind();
vbo.alloc(bufferSize);
vbo.getBuffer(0, bufferSize)
.putFloatArray(vertices)
.flush();
try (MappedBuffer buffer = vbo.getBuffer(0, bufferSize)) {
buffer.putFloatArray(vertices);
} catch (Exception e) {
Flywheel.log.error("Could not create fullscreen quad.", e);
}
vao = new GlVertexArray();
vao.bind();
GL20.glEnableVertexAttribArray(0);
glEnableVertexAttribArray(0);
GL20.glVertexAttribPointer(0, 4, GlNumericType.FLOAT.getGlEnum(), false, 4 * 4, 0);
glVertexAttribPointer(0, 4, GlNumericType.FLOAT.getGlEnum(), false, 4 * 4, 0);
GlVertexArray.unbind();
vbo.unbind();
@ -45,7 +51,7 @@ public class FullscreenQuad {
public void draw() {
vao.bind();
GL20.glDrawArrays(GL20.GL_TRIANGLES, 0, 6);
glDrawArrays(GL_TRIANGLES, 0, 6);
GlVertexArray.unbind();
}

View file

@ -0,0 +1,7 @@
package com.jozufozu.flywheel.util;
public class Unimplemented extends RuntimeException {
public Unimplemented(String message) {
super(message);
}
}