Skip to content

Instantly share code, notes, and snippets.

@WinterAlexander
Last active August 2, 2025 07:11
Show Gist options
  • Select an option

  • Save WinterAlexander/9d321557bb7a98a34f3e78352ab8ed45 to your computer and use it in GitHub Desktop.

Select an option

Save WinterAlexander/9d321557bb7a98a34f3e78352ab8ed45 to your computer and use it in GitHub Desktop.
public class SSAOFrameBuffer extends FrameBuffer {
public SSAOFrameBuffer(int width, int height, int samples) {
super(makeBuilder(width, height, samples));
}
private void checkValidBuilder() {
if(getBuilder().getSamples() > 0 && !Gdx.graphics.isGL30Available())
throw new GdxRuntimeException("Framebuffer multisample requires GLES 3.0");
if(getBuilder().getSamples() > 0 && getBuilder().getTextureAttachmentSpecs().size > 0)
throw new GdxRuntimeException("Framebuffer multisample with texture attachments not " +
"yet supported");
boolean runningGL30 = Gdx.graphics.isGL30Available();
if(!runningGL30) {
final boolean supportsPackedDepthStencil = Gdx.graphics.supportsExtension(
"GL_OES_packed_depth_stencil") || Gdx.graphics.supportsExtension(
"GL_EXT_packed_depth_stencil");
if(getBuilder().hasPackedStencilDepthRenderBuffer() && !supportsPackedDepthStencil) {
throw new GdxRuntimeException("Packed Stencil/Render render buffers are not " +
"available on GLES 2.0");
}
if(getBuilder().getTextureAttachmentSpecs().size > 1)
throw new GdxRuntimeException("Multiple render targets not available on GLES 2.0");
for(FrameBufferTextureAttachmentSpec spec : getBuilder().getTextureAttachmentSpecs()) {
if(ReflectionUtil.get(spec, "isDepth"))
throw new GdxRuntimeException("Depth texture FrameBuffer Attachment not " +
"available on GLES 2.0");
if(ReflectionUtil.get(spec, "isSencil"))
throw new GdxRuntimeException("Stencil texture FrameBuffer Attachment not " +
"available on GLES 2.0");
if(ReflectionUtil.get(spec, "isFloat")) {
if(!Gdx.graphics.supportsExtension("OES_texture_float")) {
throw new GdxRuntimeException("Float texture FrameBuffer Attachment not " +
"available on GLES 2.0");
}
}
}
}
if(getBuilder().hasPackedStencilDepthRenderBuffer()) {
if(getBuilder().hasDepthRenderBuffer() || getBuilder().hasStencilRenderBuffer())
throw new GdxRuntimeException("Frame buffer couldn't be constructed: packed " +
"stencil depth buffer cannot be specified together with separated depth or" +
" stencil buffer");
}
}
protected void build() {
GL20 gl = Gdx.gl20;
checkValidBuilder();
// iOS uses a different framebuffer handle! (not necessarily 0)
if(!defaultFramebufferHandleInitialized) {
defaultFramebufferHandleInitialized = true;
if(Gdx.app.getType() == Application.ApplicationType.iOS) {
IntBuffer intbuf =
ByteBuffer.allocateDirect(16 * Integer.SIZE / 8).order(ByteOrder.nativeOrder()).asIntBuffer();
gl.glGetIntegerv(GL20.GL_FRAMEBUFFER_BINDING, intbuf);
defaultFramebufferHandle = intbuf.get(0);
} else {
defaultFramebufferHandle = 0;
}
}
framebufferHandle = gl.glGenFramebuffer();
gl.glBindFramebuffer(GL20.GL_FRAMEBUFFER, framebufferHandle);
int width = getBuilder().getWidth();
int height = getBuilder().getHeight();
int samples = getBuilder().getSamples();
if(getBuilder().hasDepthRenderBuffer()) {
depthbufferHandle = gl.glGenRenderbuffer();
gl.glBindRenderbuffer(GL20.GL_RENDERBUFFER, depthbufferHandle);
if(samples > 0) {
org.lwjgl.opengl.GL30.glRenderbufferStorageMultisample(GL20.GL_RENDERBUFFER,
samples, ReflectionUtil.get(getBuilder().getDepthRenderBufferSpec(), "internalFormat"), width,
height);
} else {
gl.glRenderbufferStorage(GL20.GL_RENDERBUFFER,
ReflectionUtil.get(getBuilder().getDepthRenderBufferSpec(), "internalFormat"), width, height);
}
}
if(getBuilder().hasStencilRenderBuffer()) {
stencilbufferHandle = gl.glGenRenderbuffer();
gl.glBindRenderbuffer(GL20.GL_RENDERBUFFER, stencilbufferHandle);
if(samples > 0) {
org.lwjgl.opengl.GL30.glRenderbufferStorageMultisample(GL20.GL_RENDERBUFFER,
samples,
ReflectionUtil.get(getBuilder().getStencilRenderBufferSpec(), "internalFormat"), width,
height);
} else {
gl.glRenderbufferStorage(GL20.GL_RENDERBUFFER,
ReflectionUtil.get(getBuilder().getStencilRenderBufferSpec(), "internalFormat"),
width, height);
}
}
if(getBuilder().hasPackedStencilDepthRenderBuffer()) {
depthStencilPackedBufferHandle = gl.glGenRenderbuffer();
gl.glBindRenderbuffer(GL20.GL_RENDERBUFFER, depthStencilPackedBufferHandle);
if(samples > 0) {
org.lwjgl.opengl.GL30.glRenderbufferStorageMultisample(GL20.GL_RENDERBUFFER, samples,
ReflectionUtil.get(getBuilder().getPackedStencilDepthRenderBufferSpec(), "internalFormat"), width,
height);
} else {
gl.glRenderbufferStorage(GL20.GL_RENDERBUFFER,
ReflectionUtil.get(getBuilder().getPackedStencilDepthRenderBufferSpec(), "internalFormat"), width,
height);
}
hasDepthStencilPackedBuffer = true;
}
isMRT = getBuilder().getTextureAttachmentSpecs().size > 1;
int colorAttachmentCounter = 0;
if(isMRT) {
for(FrameBufferTextureAttachmentSpec attachmentSpec :
getBuilder().getTextureAttachmentSpecs()) {
Texture texture = createTexture(attachmentSpec);
textureAttachments.add(texture);
if(attachmentSpec.isColorTexture()) {
gl.glFramebufferTexture2D(GL20.GL_FRAMEBUFFER,
GL30.GL_COLOR_ATTACHMENT0 + colorAttachmentCounter, GL30.GL_TEXTURE_2D
, texture.getTextureObjectHandle(), 0);
colorAttachmentCounter++;
} else if(ReflectionUtil.get(attachmentSpec, "isDepth")) {
gl.glFramebufferTexture2D(GL20.GL_FRAMEBUFFER, GL20.GL_DEPTH_ATTACHMENT,
GL20.GL_TEXTURE_2D, texture.getTextureObjectHandle(), 0);
} else if(ReflectionUtil.get(attachmentSpec, "isStencil")) {
gl.glFramebufferTexture2D(GL20.GL_FRAMEBUFFER, GL20.GL_STENCIL_ATTACHMENT,
GL20.GL_TEXTURE_2D, texture.getTextureObjectHandle(), 0);
}
}
} else if(getBuilder().getTextureAttachmentSpecs().size > 0) {
Texture texture = createTexture(getBuilder().getTextureAttachmentSpecs().first());
textureAttachments.add(texture);
gl.glBindTexture(texture.glTarget, texture.getTextureObjectHandle());
}
for(FrameBufferRenderBufferAttachmentSpec colorBufferSpec :
getBuilder().getColorRenderBufferSpecs()) {
int colorbufferHandle = gl.glGenRenderbuffer();
gl.glBindRenderbuffer(GL20.GL_RENDERBUFFER, colorbufferHandle);
if(samples > 0) {
org.lwjgl.opengl.GL30.glRenderbufferStorageMultisample(GL20.GL_RENDERBUFFER,
samples, ReflectionUtil.get(colorBufferSpec, "internalFormat"), width, height);
} else {
gl.glRenderbufferStorage(GL20.GL_RENDERBUFFER,
ReflectionUtil.get(colorBufferSpec, "internalFormat"),
width, height);
}
Gdx.gl.glFramebufferRenderbuffer(GL20.GL_FRAMEBUFFER,
GL20.GL_COLOR_ATTACHMENT0 + colorAttachmentCounter, GL20.GL_RENDERBUFFER,
colorbufferHandle);
colorBufferHandles.add(colorbufferHandle);
colorAttachmentCounter++;
}
if(isMRT || getBuilder().getSamples() > 0) {
IntBuffer buffer = BufferUtils.newIntBuffer(colorAttachmentCounter);
ReflectionUtil.set(this, "defaultDrawBuffers", buffer);
for(int i = 0; i < colorAttachmentCounter; i++) {
buffer.put(GL30.GL_COLOR_ATTACHMENT0 + i);
}
((Buffer)buffer).position(0);
Gdx.gl30.glDrawBuffers(colorAttachmentCounter, buffer);
} else if(getBuilder().getTextureAttachmentSpecs().size > 0) {
attachFrameBufferColorTexture(textureAttachments.first());
}
if(getBuilder().hasDepthRenderBuffer()) {
gl.glFramebufferRenderbuffer(GL20.GL_FRAMEBUFFER, GL20.GL_DEPTH_ATTACHMENT,
GL20.GL_RENDERBUFFER, depthbufferHandle);
}
if(getBuilder().hasStencilRenderBuffer()) {
gl.glFramebufferRenderbuffer(GL20.GL_FRAMEBUFFER, GL20.GL_STENCIL_ATTACHMENT,
GL20.GL_RENDERBUFFER, stencilbufferHandle);
}
if(getBuilder().hasPackedStencilDepthRenderBuffer()) {
gl.glFramebufferRenderbuffer(GL20.GL_FRAMEBUFFER, GL30.GL_DEPTH_STENCIL_ATTACHMENT,
GL20.GL_RENDERBUFFER, depthStencilPackedBufferHandle);
}
gl.glBindRenderbuffer(GL20.GL_RENDERBUFFER, 0);
for(Texture texture : textureAttachments) {
gl.glBindTexture(texture.glTarget, 0);
}
int result = gl.glCheckFramebufferStatus(GL20.GL_FRAMEBUFFER);
if(result == GL20.GL_FRAMEBUFFER_UNSUPPORTED
&& getBuilder().hasDepthRenderBuffer()
&& getBuilder().hasStencilRenderBuffer()
&& (Gdx.graphics.supportsExtension("GL_OES_packed_depth_stencil")
|| Gdx.graphics.supportsExtension("GL_EXT_packed_depth_stencil"))) {
if(getBuilder().hasDepthRenderBuffer()) {
gl.glDeleteRenderbuffer(depthbufferHandle);
depthbufferHandle = 0;
}
if(getBuilder().hasStencilRenderBuffer()) {
gl.glDeleteRenderbuffer(stencilbufferHandle);
stencilbufferHandle = 0;
}
if(getBuilder().hasPackedStencilDepthRenderBuffer()) {
gl.glDeleteRenderbuffer(depthStencilPackedBufferHandle);
depthStencilPackedBufferHandle = 0;
}
depthStencilPackedBufferHandle = gl.glGenRenderbuffer();
hasDepthStencilPackedBuffer = true;
gl.glBindRenderbuffer(GL20.GL_RENDERBUFFER, depthStencilPackedBufferHandle);
if(samples > 0) {
org.lwjgl.opengl.GL30.glRenderbufferStorageMultisample(GL20.GL_RENDERBUFFER,
samples, GL_DEPTH24_STENCIL8_OES, width, height);
} else {
gl.glRenderbufferStorage(GL20.GL_RENDERBUFFER, GL_DEPTH24_STENCIL8_OES, width,
height);
}
gl.glBindRenderbuffer(GL20.GL_RENDERBUFFER, 0);
gl.glFramebufferRenderbuffer(GL20.GL_FRAMEBUFFER, GL20.GL_DEPTH_ATTACHMENT,
GL20.GL_RENDERBUFFER, depthStencilPackedBufferHandle);
gl.glFramebufferRenderbuffer(GL20.GL_FRAMEBUFFER, GL20.GL_STENCIL_ATTACHMENT,
GL20.GL_RENDERBUFFER, depthStencilPackedBufferHandle);
result = gl.glCheckFramebufferStatus(GL20.GL_FRAMEBUFFER);
}
gl.glBindFramebuffer(GL20.GL_FRAMEBUFFER, defaultFramebufferHandle);
if(result != GL20.GL_FRAMEBUFFER_COMPLETE) {
for(Texture texture : textureAttachments) {
disposeColorTexture(texture);
}
if(hasDepthStencilPackedBuffer) {
gl.glDeleteBuffer(depthStencilPackedBufferHandle);
} else {
if(getBuilder().hasDepthRenderBuffer())
gl.glDeleteRenderbuffer(depthbufferHandle);
if(getBuilder().hasStencilRenderBuffer())
gl.glDeleteRenderbuffer(stencilbufferHandle);
}
gl.glDeleteFramebuffer(framebufferHandle);
if(result == GL20.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT)
throw new IllegalStateException("Frame buffer couldn't be constructed: incomplete " +
"attachment");
if(result == GL20.GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS)
throw new IllegalStateException("Frame buffer couldn't be constructed: incomplete " +
"dimensions");
if(result == GL20.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT)
throw new IllegalStateException("Frame buffer couldn't be constructed: missing " +
"attachment");
if(result == GL20.GL_FRAMEBUFFER_UNSUPPORTED)
throw new IllegalStateException("Frame buffer couldn't be constructed: unsupported" +
" combination of formats");
if(result == GL31.GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE)
throw new IllegalStateException("Frame buffer couldn't be constructed: multisample" +
" mismatch");
throw new IllegalStateException("Frame buffer couldn't be constructed: unknown error " + result);
}
addManagedFrameBuffer(Gdx.app, this);
}
public PublicFrameBufferBuilder getBuilder() {
return (PublicFrameBufferBuilder)bufferBuilder;
}
@SuppressWarnings("rawtypes")
private static void addManagedFrameBuffer(Application app, GLFrameBuffer frameBuffer) {
Array<GLFrameBuffer> managedResources = buffers.get(app);
if(managedResources == null)
managedResources = new Array<>();
managedResources.add(frameBuffer);
buffers.put(app, managedResources);
}
private static FrameBufferBuilder makeBuilder(int width, int height, int samples) {
PublicFrameBufferBuilder frameBufferBuilder = new PublicFrameBufferBuilder(width, height,
samples);
frameBufferBuilder.addBasicColorTextureAttachment(Pixmap.Format.RGB888);
frameBufferBuilder.addBasicDepthRenderBuffer();
return frameBufferBuilder;
}
public static class PublicFrameBufferBuilder extends FrameBufferBuilder {
public PublicFrameBufferBuilder(int width, int height, int samples) {
super(width, height, samples);
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public int getSamples() {
return samples;
}
public boolean hasDepthRenderBuffer() {
return hasDepthRenderBuffer;
}
public boolean hasStencilRenderBuffer() {
return hasStencilRenderBuffer;
}
public boolean hasPackedStencilDepthRenderBuffer() {
return hasPackedStencilDepthRenderBuffer;
}
public Array<FrameBufferTextureAttachmentSpec> getTextureAttachmentSpecs() {
return textureAttachmentSpecs;
}
public Array<FrameBufferRenderBufferAttachmentSpec> getColorRenderBufferSpecs() {
return colorRenderBufferSpecs;
}
public FrameBufferRenderBufferAttachmentSpec getStencilRenderBufferSpec() {
return stencilRenderBufferSpec;
}
public FrameBufferRenderBufferAttachmentSpec getDepthRenderBufferSpec() {
return depthRenderBufferSpec;
}
public FrameBufferRenderBufferAttachmentSpec getPackedStencilDepthRenderBufferSpec() {
return packedStencilDepthRenderBufferSpec;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment