diff --git a/src/common/pixelformat.cpp b/src/common/pixelformat.cpp index f404d3642..a6334aa54 100644 --- a/src/common/pixelformat.cpp +++ b/src/common/pixelformat.cpp @@ -53,7 +53,12 @@ static StringMap::Entry formatEntries[] = { "rgb10a2", PIXELFORMAT_RGB10A2 }, { "rg11b10f", PIXELFORMAT_RG11B10F }, - { "stencil8", PIXELFORMAT_STENCIL8 }, + { "stencil8", PIXELFORMAT_STENCIL8 }, + { "depth16", PIXELFORMAT_DEPTH16 }, + { "depth24", PIXELFORMAT_DEPTH24 }, + { "depth32f", PIXELFORMAT_DEPTH32F }, + { "depth24stencil8", PIXELFORMAT_DEPTH24_STENCIL8 }, + { "depth32fstencil8", PIXELFORMAT_DEPTH32F_STENCIL8 }, { "DXT1", PIXELFORMAT_DXT1 }, { "DXT3", PIXELFORMAT_DXT3 }, @@ -116,7 +121,8 @@ bool isPixelFormatCompressed(PixelFormat format) bool isPixelFormatDepthStencil(PixelFormat format) { - return format == PIXELFORMAT_STENCIL8; + int iformat = (int) format; + return iformat >= (int) PIXELFORMAT_STENCIL8 && iformat <= (int) PIXELFORMAT_DEPTH32F_STENCIL8; } size_t getPixelFormatSize(PixelFormat format) @@ -133,6 +139,7 @@ size_t getPixelFormatSize(PixelFormat format) case PIXELFORMAT_RGBA4: case PIXELFORMAT_RGB5A1: case PIXELFORMAT_RGB565: + case PIXELFORMAT_DEPTH16: return 2; case PIXELFORMAT_RGBA8: case PIXELFORMAT_sRGBA8: @@ -141,10 +148,14 @@ size_t getPixelFormatSize(PixelFormat format) case PIXELFORMAT_R32F: case PIXELFORMAT_RGB10A2: case PIXELFORMAT_RG11B10F: + case PIXELFORMAT_DEPTH24: + case PIXELFORMAT_DEPTH32F: + case PIXELFORMAT_DEPTH24_STENCIL8: return 4; case PIXELFORMAT_RGBA16: case PIXELFORMAT_RGBA16F: case PIXELFORMAT_RG32F: + case PIXELFORMAT_DEPTH32F_STENCIL8: return 8; case PIXELFORMAT_RGBA32F: return 16; diff --git a/src/common/pixelformat.h b/src/common/pixelformat.h index 71765aa0a..37dde8a00 100644 --- a/src/common/pixelformat.h +++ b/src/common/pixelformat.h @@ -59,6 +59,11 @@ enum PixelFormat // depth/stencil formats PIXELFORMAT_STENCIL8, + PIXELFORMAT_DEPTH16, + PIXELFORMAT_DEPTH24, + PIXELFORMAT_DEPTH32F, + PIXELFORMAT_DEPTH24_STENCIL8, + PIXELFORMAT_DEPTH32F_STENCIL8, // compressed formats PIXELFORMAT_DXT1, diff --git a/src/modules/graphics/opengl/Canvas.cpp b/src/modules/graphics/opengl/Canvas.cpp index add3a98c8..887aef371 100644 --- a/src/modules/graphics/opengl/Canvas.cpp +++ b/src/modules/graphics/opengl/Canvas.cpp @@ -30,7 +30,7 @@ namespace graphics namespace opengl { -static GLenum createFBO(GLuint &framebuffer, TextureType texType, GLuint texture, int layers, bool initialize) +static GLenum createFBO(GLuint &framebuffer, TextureType texType, PixelFormat format, GLuint texture, int layers) { // get currently bound fbo to reset to it later GLuint current_fbo = gl.getFramebuffer(OpenGL::FRAMEBUFFER_ALL); @@ -40,27 +40,35 @@ static GLenum createFBO(GLuint &framebuffer, TextureType texType, GLuint texture if (texture != 0) { - if (initialize) - { - int faces = texType == TEXTURE_CUBE ? 6 : 1; + bool unusedSRGB = false; + OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, false, unusedSRGB); - // Make sure all faces and layers of the texture are initialized to - // transparent black. This is unfortunately probably pretty slow for - // 2D-array and 3D textures with a lot of layers... - for (int layer = layers - 1; layer >= 0; layer--) + int faces = texType == TEXTURE_CUBE ? 6 : 1; + + // Make sure all faces and layers of the texture are initialized to + // transparent black. This is unfortunately probably pretty slow for + // 2D-array and 3D textures with a lot of layers... + for (int layer = layers - 1; layer >= 0; layer--) + { + for (int face = faces - 1; face >= 0; face--) { - for (int face = faces - 1; face >= 0; face--) + for (GLenum attachment : fmt.framebufferAttachments) { - gl.framebufferTexture(GL_COLOR_ATTACHMENT0, texType, texture, 0, layer, face); - glClearColor(0.0f, 0.0f, 0.0f, 0.0f); - glClear(GL_COLOR_BUFFER_BIT); + if (attachment == GL_NONE) + continue; + + gl.framebufferTexture(attachment, texType, texture, 0, layer, face); + + if (isPixelFormatDepthStencil(format)) + glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + else + { + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + glClear(GL_COLOR_BUFFER_BIT); + } } } } - else - { - gl.framebufferTexture(GL_COLOR_ATTACHMENT0, texType, texture, 0, 0, 0); - } } GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); @@ -90,7 +98,11 @@ static bool createRenderbuffer(int width, int height, int &samples, PixelFormat else glRenderbufferStorage(GL_RENDERBUFFER, fmt.internalformat, width, height); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, fmt.framebufferAttachments[0], GL_RENDERBUFFER, buffer); + for (GLenum attachment : fmt.framebufferAttachments) + { + if (attachment != GL_NONE) + glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, buffer); + } if (samples > 1) glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_SAMPLES, &samples); @@ -103,9 +115,14 @@ static bool createRenderbuffer(int width, int height, int &samples, PixelFormat if (status == GL_FRAMEBUFFER_COMPLETE && (reqsamples <= 1 || samples > 1)) { - // Initialize the buffer to transparent black. - glClearColor(0.0f, 0.0f, 0.0f, 0.0f); - glClear(GL_COLOR_BUFFER_BIT); + if (isPixelFormatDepthStencil(pixelformat)) + glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + else + { + // Initialize the buffer to transparent black. + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + glClear(GL_COLOR_BUFFER_BIT); + } } else { @@ -129,17 +146,17 @@ Canvas::Canvas(const Settings &settings) , actualSamples(0) , textureMemory(0) { - this->width = settings.width; - this->height = settings.height; - this->pixelWidth = (int) ((width * settings.pixeldensity) + 0.5); - this->pixelHeight = (int) ((height * settings.pixeldensity) + 0.5); + width = settings.width; + height = settings.height; + pixelWidth = (int) ((width * settings.pixeldensity) + 0.5); + pixelHeight = (int) ((height * settings.pixeldensity) + 0.5); if (texType == TEXTURE_VOLUME) - this->depth = settings.layers; + depth = settings.layers; else if (texType == TEXTURE_2D_ARRAY) - this->layers = settings.layers; + layers = settings.layers; else - this->layers = 1; + layers = 1; if (width <= 0 || height <= 0 || layers <= 0) throw love::Exception("Canvas dimensions must be greater than 0."); @@ -147,9 +164,8 @@ Canvas::Canvas(const Settings &settings) if (texType != TEXTURE_2D && settings.msaa > 1) throw love::Exception("MSAA is only supported for Canvases with the 2D texture type."); - this->format = getSizedFormat(settings.format); - - readable = this->format != PIXELFORMAT_STENCIL8; + format = getSizedFormat(settings.format); + readable = !isPixelFormatDepthStencil(format); initQuad(); loadVolatile(); @@ -263,7 +279,7 @@ bool Canvas::loadVolatile() } // Create a canvas-local FBO used for glReadPixels as well as MSAA blitting. - status = createFBO(fbo, texType, texture, texType == TEXTURE_VOLUME ? depth : layers, true); + status = createFBO(fbo, texType, format, texture, texType == TEXTURE_VOLUME ? depth : layers); if (status != GL_FRAMEBUFFER_COMPLETE) { @@ -468,14 +484,17 @@ bool Canvas::isMultiFormatMultiCanvasSupported() return gl.getMaxRenderTargets() > 1 && (GLAD_ES_VERSION_3_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object); } -bool Canvas::supportedFormats[] = {false}; -bool Canvas::checkedFormats[] = {false}; +Canvas::SupportedFormat Canvas::supportedFormats[] = {}; +Canvas::SupportedFormat Canvas::checkedFormats[] = {}; bool Canvas::isFormatSupported(PixelFormat format) { if (!isSupported()) return false; + const char *fstr = "?"; + love::getConstant(format, fstr); + bool supported = true; format = getSizedFormat(format); @@ -485,16 +504,32 @@ bool Canvas::isFormatSupported(PixelFormat format) if (!OpenGL::isPixelFormatSupported(format, true, readable, false)) return false; - if (checkedFormats[format]) - return supportedFormats[format]; + if (checkedFormats[format].get(readable)) + return supportedFormats[format].get(readable); // Even though we might have the necessary OpenGL version or extension, // drivers are still allowed to throw FRAMEBUFFER_UNSUPPORTED when attaching // a texture to a FBO whose format the driver doesn't like. So we should // test with an actual FBO. + GLuint texture = 0; + GLuint renderbuffer = 0; + + bool unusedSRGB = false; + OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, readable, unusedSRGB); + + GLuint current_fbo = gl.getFramebuffer(OpenGL::FRAMEBUFFER_ALL); + + GLuint fbo = 0; + glGenFramebuffers(1, &fbo); + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, fbo); + + // Make sure at least something is bound to a color attachment. I believe + // this is required on ES2 but I'm not positive. + if (depthstencil) + gl.framebufferTexture(GL_COLOR_ATTACHMENT0, TEXTURE_2D, gl.getDefaultTexture(TEXTURE_2D), 0, 0, 0); + if (readable) { - GLuint texture = 0; glGenTextures(1, &texture); gl.bindTextureToUnit(TEXTURE_2D, texture, 0, false); @@ -505,28 +540,41 @@ bool Canvas::isFormatSupported(PixelFormat format) Texture::Wrap w; gl.setTextureWrap(TEXTURE_2D, w); - bool unusedSRGB = false; - gl.rawTexStorage(TEXTURE_2D, 1, format, unusedSRGB, 2, 2); - - GLuint fbo = 0; - supported = (createFBO(fbo, TEXTURE_2D, texture, 1, false) == GL_FRAMEBUFFER_COMPLETE); - gl.deleteFramebuffer(fbo); - - gl.deleteTexture(texture); + unusedSRGB = false; + gl.rawTexStorage(TEXTURE_2D, 1, format, unusedSRGB, 1, 1); } else { - int samples = 0; - GLuint renderbuffer = 0; - supported = createRenderbuffer(2, 2, samples, format, renderbuffer); - - if (supported) - glDeleteRenderbuffers(1, &renderbuffer); + glGenRenderbuffers(1, &renderbuffer); + glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer); + glRenderbufferStorage(GL_RENDERBUFFER, fmt.internalformat, 1, 1); } + for (GLenum attachment : fmt.framebufferAttachments) + { + if (attachment == GL_NONE) + continue; + + if (readable) + gl.framebufferTexture(attachment, TEXTURE_2D, texture, 0, 0, 0); + else + glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, renderbuffer); + } + + supported = glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE; + + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, current_fbo); + gl.deleteFramebuffer(fbo); + + if (texture != 0) + gl.deleteTexture(texture); + + if (renderbuffer != 0) + glDeleteRenderbuffers(1, &renderbuffer); + // Cache the result so we don't do this for every isFormatSupported call. - checkedFormats[format] = true; - supportedFormats[format] = supported; + checkedFormats[format].set(readable, true); + supportedFormats[format].set(readable, supported); return supported; } diff --git a/src/modules/graphics/opengl/Canvas.h b/src/modules/graphics/opengl/Canvas.h index 439901d0b..7936fd20f 100644 --- a/src/modules/graphics/opengl/Canvas.h +++ b/src/modules/graphics/opengl/Canvas.h @@ -86,6 +86,25 @@ public: private: + struct SupportedFormat + { + bool readable = false; + bool nonreadable = false; + + bool get(bool getreadable) + { + return getreadable ? readable : nonreadable; + } + + void set(bool setreadable, bool val) + { + if (setreadable) + readable = val; + else + nonreadable = val; + } + }; + GLuint fbo; GLuint texture; @@ -98,8 +117,8 @@ private: size_t textureMemory; - static bool supportedFormats[PIXELFORMAT_MAX_ENUM]; - static bool checkedFormats[PIXELFORMAT_MAX_ENUM]; + static SupportedFormat supportedFormats[PIXELFORMAT_MAX_ENUM]; + static SupportedFormat checkedFormats[PIXELFORMAT_MAX_ENUM]; }; // Canvas diff --git a/src/modules/graphics/opengl/OpenGL.cpp b/src/modules/graphics/opengl/OpenGL.cpp index 09293b20a..e04a0c5d9 100644 --- a/src/modules/graphics/opengl/OpenGL.cpp +++ b/src/modules/graphics/opengl/OpenGL.cpp @@ -1343,6 +1343,60 @@ OpenGL::TextureFormat OpenGL::convertPixelFormat(PixelFormat pixelformat, bool r } break; + case PIXELFORMAT_DEPTH16: + f.internalformat = GL_DEPTH_COMPONENT16; + f.externalformat = GL_DEPTH_COMPONENT; + f.type = GL_UNSIGNED_SHORT; + f.framebufferAttachments[0] = GL_DEPTH_ATTACHMENT; + break; + + case PIXELFORMAT_DEPTH24: + if (GLAD_ES_VERSION_2_0 && !GLAD_ES_VERSION_3_0 && !GLAD_OES_depth24 && GLAD_OES_packed_depth_stencil) + { + f.internalformat = GL_DEPTH24_STENCIL8; + f.externalformat = GL_DEPTH_STENCIL; + f.type = GL_UNSIGNED_INT_24_8; + f.framebufferAttachments[0] = GL_DEPTH_ATTACHMENT; + f.framebufferAttachments[1] = GL_STENCIL_ATTACHMENT; + } + else + { + f.internalformat = GL_DEPTH_COMPONENT24; + f.externalformat = GL_DEPTH_COMPONENT; + f.type = GL_UNSIGNED_INT; + f.framebufferAttachments[0] = GL_DEPTH_ATTACHMENT; + } + break; + + case PIXELFORMAT_DEPTH32F: + f.internalformat = GL_DEPTH_COMPONENT32F; + f.externalformat = GL_DEPTH_COMPONENT; + f.type = GL_FLOAT; + f.framebufferAttachments[0] = GL_DEPTH_ATTACHMENT; + break; + + case PIXELFORMAT_DEPTH24_STENCIL8: + f.internalformat = GL_DEPTH24_STENCIL8; + f.externalformat = GL_DEPTH_STENCIL; + f.type = GL_UNSIGNED_INT_24_8; + if (GLAD_ES_VERSION_3_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object) + { + f.framebufferAttachments[0] = GL_DEPTH_STENCIL_ATTACHMENT; + } + else if (GLAD_EXT_packed_depth_stencil || GLAD_OES_packed_depth_stencil) + { + f.framebufferAttachments[0] = GL_DEPTH_ATTACHMENT; + f.framebufferAttachments[1] = GL_STENCIL_ATTACHMENT; + } + break; + + case PIXELFORMAT_DEPTH32F_STENCIL8: + f.internalformat = GL_DEPTH32F_STENCIL8; + f.externalformat = GL_DEPTH_STENCIL; + f.type = GL_FLOAT_32_UNSIGNED_INT_24_8_REV; + f.framebufferAttachments[0] = GL_DEPTH_STENCIL_ATTACHMENT; + break; + case PIXELFORMAT_DXT1: f.internalformat = isSRGB ? GL_COMPRESSED_SRGB_S3TC_DXT1_EXT : GL_COMPRESSED_RGB_S3TC_DXT1_EXT; break; @@ -1580,6 +1634,19 @@ bool OpenGL::isPixelFormatSupported(PixelFormat pixelformat, bool rendertarget, case PIXELFORMAT_STENCIL8: return rendertarget && !readable; + case PIXELFORMAT_DEPTH16: + return rendertarget && !readable; + + case PIXELFORMAT_DEPTH24: + return rendertarget && !readable && (GLAD_VERSION_2_0 || GLAD_ES_VERSION_3_0 || GLAD_OES_depth24 || GLAD_OES_packed_depth_stencil); + + case PIXELFORMAT_DEPTH24_STENCIL8: + return rendertarget && !readable && (GLAD_VERSION_3_0 || GLAD_ES_VERSION_3_0 || GLAD_EXT_packed_depth_stencil || GLAD_OES_packed_depth_stencil); + + case PIXELFORMAT_DEPTH32F: + case PIXELFORMAT_DEPTH32F_STENCIL8: + return rendertarget && !readable && (GLAD_VERSION_3_0 || GLAD_ES_VERSION_3_0 || GLAD_ARB_depth_buffer_float); + case PIXELFORMAT_DXT1: return GLAD_EXT_texture_compression_s3tc || GLAD_EXT_texture_compression_dxt1; case PIXELFORMAT_DXT3: