diff --git a/changes.txt b/changes.txt index 03c4b8705..7ff5e573c 100644 --- a/changes.txt +++ b/changes.txt @@ -16,6 +16,7 @@ Released: N/A * Added Shader:hasUniform. * Added support for non-square shader uniform matrices on desktop platforms. * Added Shader:send(matrixname, is_column_major, matrix, ...) which specifies how to interpret the matrix table arguments. + * Added love.graphics.captureScreenshot (replaces love.graphics.newScreenshot). * Added love.window.updateMode. * Added Object:release. * Added queueable audio sources. @@ -30,15 +31,16 @@ Released: N/A * Changed all color values to be in the range 0-1, rather than 0-255. * Changed love.graphics.print and friends to ignore carriage returns. * Changed the 'multiply' blend mode to error if not used with the 'premultiplied' blend alpha mode, since the formula only works with that anyway. + * Changed some love.graphics, love.window, and love.event APIs to cause an error if a Canvas is active. * Changed the audio playback APIs drastically. * Changed enet to no longer set the 'enet' global, again matching luasocket. * Changed Source seeking behaviour, all kinds of Sources now behave similarly when seeking past the boundaries. - * Changed the rendering API to one based on render passes. * Removed the default source type for love.audio.newSource. * Removed variant of love.filesystem.newFileData which takes base64 data, use love.math.decode instead. * Removed the no-argument variant of Text:set, use Text:clear instead. * Removed Shader:getExternVariable, use Shader:hasUniform instead. + * Removed love.graphics.newScreenshot, use love.graphics.captureScreenshot instead. * Removed deprecated enet function host:socket_get_address. * Removed functions deprecated in LÖVE 0.10.2: * Removed Shader:sendInt, Shader:sendBoolean, Shader:sentFloat, Shader:sendMatrix, and Shader:sendTexture (use Shader:send instead). diff --git a/src/modules/event/sdl/Event.cpp b/src/modules/event/sdl/Event.cpp index 82e26a72c..2f4abab13 100644 --- a/src/modules/event/sdl/Event.cpp +++ b/src/modules/event/sdl/Event.cpp @@ -157,11 +157,11 @@ void Event::exceptionIfInRenderPass() { // Some core OS graphics functionality (e.g. swap buffers on some platforms) // happens inside SDL_PumpEvents - which is called by SDL_PollEvent and - // friends. It's probably a bad idea to call those functions while we're in - // a render pass. + // friends. It's probably a bad idea to call those functions while a Canvas + // is active. auto gfx = Module::getInstance(Module::M_GRAPHICS); - if (gfx != nullptr && gfx->isPassActive()) - throw love::Exception("Cannot call this function while a render pass is active in love.graphics."); + if (gfx != nullptr && gfx->isCanvasActive()) + throw love::Exception("Cannot call this function while a Canvas is active in love.graphics."); } Message *Event::convert(const SDL_Event &e) diff --git a/src/modules/graphics/Graphics.h b/src/modules/graphics/Graphics.h index 0460621d9..5944088c4 100644 --- a/src/modules/graphics/Graphics.h +++ b/src/modules/graphics/Graphics.h @@ -61,14 +61,6 @@ void gammaCorrectColor(Colorf &c); **/ void unGammaCorrectColor(Colorf &c); -class RenderOutsidePassException : public love::Exception -{ -public: - RenderOutsidePassException() - : Exception("Cannot draw outside of a render pass!") - {} -}; - class Graphics : public Module { public: @@ -193,7 +185,7 @@ public: struct Stats { int drawCalls; - int renderPasses; + int canvasSwitches; int shaderSwitches; int canvases; int images; @@ -224,6 +216,12 @@ public: } }; + struct OptionalColorf + { + Colorf c; + bool enabled; + }; + virtual ~Graphics(); // Implements Module. @@ -261,7 +259,7 @@ public: **/ virtual bool isActive() const = 0; - virtual bool isPassActive() const = 0; + virtual bool isCanvasActive() const = 0; static bool getConstant(const char *in, DrawMode &out); static bool getConstant(DrawMode in, const char *&out); diff --git a/src/modules/graphics/opengl/Canvas.cpp b/src/modules/graphics/opengl/Canvas.cpp index 871b77a1b..593637d4f 100644 --- a/src/modules/graphics/opengl/Canvas.cpp +++ b/src/modules/graphics/opengl/Canvas.cpp @@ -253,15 +253,8 @@ void Canvas::unloadVolatile() void Canvas::drawv(const Matrix4 &t, const Vertex *v) { auto gfx = Module::getInstance(Module::M_GRAPHICS); - if (gfx != nullptr) - { - const PassInfo &info = gfx->getActivePass(); - for (const auto &attachment : info.colorAttachments) - { - if (attachment.canvas == this) - throw love::Exception("Cannot render a Canvas to itself!"); - } - } + if (gfx != nullptr && gfx->isCanvasActive(this)) + throw love::Exception("Cannot render a Canvas to itself!"); OpenGL::TempDebugGroup debuggroup("Canvas draw"); @@ -334,6 +327,59 @@ const void *Canvas::getHandle() const return &texture; } +love::image::ImageData *Canvas::newImageData(love::image::Image *module, int x, int y, int w, int h) +{ + if (x < 0 || y < 0 || w <= 0 || h <= 0 || (x + w) > getWidth() || (y + h) > getHeight()) + throw love::Exception("Invalid rectangle dimensions."); + + Graphics *gfx = Module::getInstance(Module::M_GRAPHICS); + + if (gfx != nullptr) + { + for (const auto &c : gfx->getCanvas()) + { + if (c == this) + throw love::Exception("Canvas:newImageData cannot be called while that Canvas is currently active."); + } + } + + PixelFormat dataformat; + switch (getPixelFormat()) + { + case PIXELFORMAT_RGB10A2: // FIXME: Conversions aren't supported in GLES + dataformat = PIXELFORMAT_RGBA16; + break; + case PIXELFORMAT_R16F: + case PIXELFORMAT_RG16F: + case PIXELFORMAT_RGBA16F: + case PIXELFORMAT_RG11B10F: // FIXME: Conversions aren't supported in GLES + dataformat = PIXELFORMAT_RGBA16F; + break; + case PIXELFORMAT_R32F: + case PIXELFORMAT_RG32F: + case PIXELFORMAT_RGBA32F: + dataformat = PIXELFORMAT_RGBA32F; + break; + default: + dataformat = PIXELFORMAT_RGBA8; + break; + } + + love::image::ImageData *imagedata = module->newImageData(w, h, dataformat); + + bool isSRGB = false; + OpenGL::TextureFormat fmt = gl.convertPixelFormat(dataformat, false, isSRGB); + + GLuint current_fbo = gl.getFramebuffer(OpenGL::FRAMEBUFFER_ALL); + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, getFBO()); + + glReadPixels(x, y, w, h, fmt.externalformat, fmt.type, imagedata->getData()); + + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, current_fbo); + + return imagedata; +} + PixelFormat Canvas::getSizedFormat(PixelFormat format) { switch (format) diff --git a/src/modules/graphics/opengl/Canvas.h b/src/modules/graphics/opengl/Canvas.h index cb19d00d6..a041e06f0 100644 --- a/src/modules/graphics/opengl/Canvas.h +++ b/src/modules/graphics/opengl/Canvas.h @@ -61,6 +61,8 @@ public: bool setWrap(const Texture::Wrap &w) override; const void *getHandle() const override; + love::image::ImageData *newImageData(love::image::Image *module, int x, int y, int w, int h); + inline GLenum getStatus() const { return status; diff --git a/src/modules/graphics/opengl/Graphics.cpp b/src/modules/graphics/opengl/Graphics.cpp index 87a9e4bba..c2cc8fa95 100644 --- a/src/modules/graphics/opengl/Graphics.cpp +++ b/src/modules/graphics/opengl/Graphics.cpp @@ -58,10 +58,8 @@ Graphics::Graphics() , height(0) , created(false) , active(true) - , canCaptureScreenshot(true) - , currentPass() , writingToStencil(false) - , renderPassCount(0) + , canvasSwitchCount(0) { gl = OpenGL(); @@ -132,6 +130,7 @@ void Graphics::restoreState(const DisplayState &s) setFont(s.font.get()); setShader(s.shader.get()); + setCanvas(s.canvases); setColorMask(s.colorMask); setWireframe(s.wireframe); @@ -174,6 +173,22 @@ void Graphics::restoreStateChecked(const DisplayState &s) setFont(s.font.get()); setShader(s.shader.get()); + bool canvaseschanged = s.canvases.size() != cur.canvases.size(); + if (!canvaseschanged) + { + for (size_t i = 0; i < s.canvases.size() && i < cur.canvases.size(); i++) + { + if (s.canvases[i].get() != cur.canvases[i].get()) + { + canvaseschanged = true; + break; + } + } + } + + if (canvaseschanged) + setCanvas(s.canvases); + if (s.colorMask != cur.colorMask) setColorMask(s.colorMask); @@ -211,7 +226,7 @@ void Graphics::setViewportSize(int width, int height) this->width = width; this->height = height; - if (currentPass.active && currentPass.info.colorAttachmentCount == 0) + if (states.back().canvases.empty()) { // Set the viewport to top-left corner. gl.setViewport({0, 0, width, height}, false); @@ -427,97 +442,72 @@ void Graphics::reset() origin(); } -void Graphics::beginPass(PassInfo::BeginAction beginAction, Colorf clearColor) +void Graphics::setCanvas(Canvas *canvas) { - if (currentPass.active) - throw love::Exception("Cannot call beginPass while another render pass is active!"); + if (canvas == nullptr) + return setCanvas(); - currentPass.active = true; - - OpenGL::TempDebugGroup debuggroup("Render Pass begin"); - - gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, gl.getDefaultFBO()); - - gl.setViewport({0, 0, width, height}, false); - - // The projection matrix is flipped compared to rendering to a canvas, due - // to OpenGL considering (0,0) bottom-left instead of top-left. - gl.matrices.projection = Matrix4::ortho(0.0, (float) width, (float) height, 0.0); - - if (GLAD_VERSION_1_0 || GLAD_EXT_sRGB_write_control) - { - if (isGammaCorrect() && !gl.hasFramebufferSRGB()) - gl.setFramebufferSRGB(true); - else if (!isGammaCorrect() && gl.hasFramebufferSRGB()) - gl.setFramebufferSRGB(false); - } - - // Always clear the stencil buffer, for now. - GLbitfield clearflags = GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT; - - if (beginAction == PassInfo::BEGIN_CLEAR) - { - Colorf c = clearColor; - gammaCorrectColor(c); - - glClearColor(c.r, c.g, c.b, c.a); - clearflags |= GL_COLOR_BUFFER_BIT; - } - - if (clearflags != 0) - glClear(clearflags); - - PassInfo info; - info.colorAttachmentCount = 0; - info.stencil = true; - - currentPass.info = info; - - renderPassCount++; - - canCaptureScreenshot = false; + std::vector canvases = {canvas}; + setCanvas(canvases); } -void Graphics::beginPass(const PassInfo &info) +void Graphics::setCanvas(const std::vector &canvases) { - if (info.colorAttachmentCount == 0) - throw love::Exception("At least one Canvas must be specified for an off-screen render pass."); + DisplayState &state = states.back(); + int ncanvases = (int) canvases.size(); - if (currentPass.active) - throw love::Exception("Cannot call beginPass while another render pass is active!"); + if (ncanvases == 0) + return setCanvas(); - if (info.colorAttachmentCount > gl.getMaxRenderTargets()) - throw love::Exception("This system can't simultaneously render to %d canvases.", info.colorAttachmentCount); + if (ncanvases == (int) state.canvases.size()) + { + bool modified = false; - Canvas *firstcanvas = info.colorAttachments[0].canvas; + for (int i = 0; i < ncanvases; i++) + { + if (canvases[i] != state.canvases[i].get()) + { + modified = true; + break; + } + } - bool multiformatsupported = isSupported(Feature::FEATURE_MULTI_CANVAS_FORMATS); + if (!modified) + return; + } + + if (ncanvases > gl.getMaxRenderTargets()) + throw love::Exception("This system can't simultaneously render to %d canvases.", ncanvases); + + Canvas *firstcanvas = canvases[0]; + + bool multiformatsupported = Canvas::isMultiFormatMultiCanvasSupported(); PixelFormat firstformat = firstcanvas->getPixelFormat(); bool hasSRGBcanvas = firstformat == PIXELFORMAT_sRGBA8; - for (int i = 1; i < info.colorAttachmentCount; i++) + for (int i = 1; i < ncanvases; i++) { - Canvas *c = info.colorAttachments[i].canvas; + Canvas *c = canvases[i]; if (c->getWidth() != firstcanvas->getWidth() || c->getHeight() != firstcanvas->getHeight()) - throw love::Exception("All canvases in a render pass must have the same dimensions."); + throw love::Exception("All canvases in must have the same dimensions."); if (!multiformatsupported && c->getPixelFormat() != firstformat) throw love::Exception("This system doesn't support multi-canvas rendering with different canvas formats."); if (c->getRequestedMSAA() != firstcanvas->getRequestedMSAA()) - throw love::Exception("All Canvases in a render pass must have the same requested MSAA value."); + throw love::Exception("All Canvases in must have the same requested MSAA value."); if (c->getPixelFormat() == PIXELFORMAT_sRGBA8) hasSRGBcanvas = true; } - OpenGL::TempDebugGroup debuggroup("Render Pass begin"); + OpenGL::TempDebugGroup debuggroup("setCanvas(...)"); - bindCachedFBOForPass(info); + endPass(); - currentPass.active = true; + bindCachedFBO(canvases); int w = firstcanvas->getWidth(); int h = firstcanvas->getHeight(); @@ -534,73 +524,102 @@ void Graphics::beginPass(const PassInfo &info) gl.setFramebufferSRGB(false); } - GLbitfield clearflags = 0; + std::vector> canvasrefs; + canvasrefs.reserve(canvases.size()); - // Take a single-color clear codepath if there's only one specified Canvas. - // The multi-color codepath (in the loop below) assumes MRT functions are - // available, and also may call more expensive GL functions which are - // unnecessary if only one Canvas is used. - if (info.colorAttachmentCount <= 1) + for (Canvas *c : canvases) + canvasrefs.push_back(c); + + std::swap(state.canvases, canvasrefs); + + canvasSwitchCount++; +} + +void Graphics::setCanvas(const std::vector> &canvases) +{ + std::vector canvaslist; + canvaslist.reserve(canvases.size()); + + for (const StrongRef &c : canvases) + canvaslist.push_back(c.get()); + + return setCanvas(canvaslist); +} + +void Graphics::setCanvas() +{ + DisplayState &state = states.back(); + + if (state.canvases.empty()) + return; + + OpenGL::TempDebugGroup debuggroup("setCanvas()"); + + endPass(); + + state.canvases.clear(); + + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, gl.getDefaultFBO()); + gl.setViewport({0, 0, width, height}, false); + + // The projection matrix is flipped compared to rendering to a canvas, due + // to OpenGL considering (0,0) bottom-left instead of top-left. + gl.matrices.projection = Matrix4::ortho(0.0, (float) width, (float) height, 0.0); + + if (GLAD_VERSION_1_0 || GLAD_EXT_sRGB_write_control) { - if (info.colorAttachmentCount > 0 && info.colorAttachments[0].beginAction == PassInfo::BEGIN_CLEAR) - { - clearflags |= GL_COLOR_BUFFER_BIT; - Colorf c = info.colorAttachments[0].clearColor; - gammaCorrectColor(c); - glClearColor(c.r, c.g, c.b, c.a); - } - } - else - { - bool drawbuffermodified = false; - - for (int i = 0; i < info.colorAttachmentCount; i++) - { - if (info.colorAttachments[i].beginAction == PassInfo::BEGIN_CLEAR) - { - Colorf c = info.colorAttachments[i].clearColor; - gammaCorrectColor(c); - - if (GLAD_ES_VERSION_3_0 || GLAD_VERSION_3_0) - { - const GLfloat carray[] = {c.r, c.g, c.b, c.a}; - glClearBufferfv(GL_COLOR, i, carray); - } - else - { - glDrawBuffer(GL_COLOR_ATTACHMENT0 + i); - glClearColor(c.r, c.g, c.b, c.a); - glClear(GL_COLOR_BUFFER_BIT); - - drawbuffermodified = true; - } - } - } - - // Revert to the expected draw buffers once we're done, if glClearBuffer - // wasn't supported. - if (drawbuffermodified) - { - std::vector bufs; - - for (int i = 0; i < info.colorAttachmentCount; i++) - bufs.push_back(GL_COLOR_ATTACHMENT0 + i); - - glDrawBuffers((int) bufs.size(), &bufs[0]); - } + if (isGammaCorrect() && !gl.hasFramebufferSRGB()) + gl.setFramebufferSRGB(true); + else if (!isGammaCorrect() && gl.hasFramebufferSRGB()) + gl.setFramebufferSRGB(false); } - if (info.stencil) - clearflags |= GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT; + canvasSwitchCount++; +} - if (clearflags != 0) - glClear(clearflags); +std::vector Graphics::getCanvas() const +{ + std::vector canvases; + canvases.reserve(states.back().canvases.size()); - for (int i = 0; i < info.colorAttachmentCount; i++) - info.colorAttachments[i].canvas->retain(); + for (const StrongRef &c : states.back().canvases) + canvases.push_back(c.get()); - currentPass.info = info; - renderPassCount++; + return canvases; +} + +void Graphics::endPass() +{ + // Discard the stencil buffer. + discard({}, true); + + auto &canvases = states.back().canvases; + + // Resolve MSAA buffers. + if (canvases.size() > 0 && canvases[0]->getMSAA() > 1) + { + int w = canvases[0]->getWidth(); + int h = canvases[0]->getHeight(); + + for (int i = 0; i < (int) canvases.size(); i++) + { + glReadBuffer(GL_COLOR_ATTACHMENT0 + i); + + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_DRAW, canvases[i]->getFBO()); + + if (GLAD_APPLE_framebuffer_multisample) + glResolveMultisampleFramebufferAPPLE(); + else + glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_NEAREST); + } + } +} + +void Graphics::clear(Colorf c) +{ + gammaCorrectColor(c); + glClearColor(c.r, c.g, c.b, c.a); + glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); if (gl.bugs.clearRequiresDriverTextureStateUpdate && Shader::current) { @@ -611,123 +630,89 @@ void Graphics::beginPass(const PassInfo &info) } } -void Graphics::endPass() +void Graphics::clear(const std::vector &colors) { - endPass(0, 0, 0, 0, nullptr, nullptr); -} + if (colors.size() == 0) + return; -void Graphics::endPass(int sX, int sY, int sW, int sH, const ScreenshotInfo *info, void *screenshotCallbackData) -{ - if (!currentPass.active) - return; // Should this error instead? + int ncanvases = (int) states.back().canvases.size(); + int ncolors = std::min((int) colors.size(), ncanvases); - StrongRef imagedata; - - auto &attachments = currentPass.info.colorAttachments; - int attachmentcount = currentPass.info.colorAttachmentCount; - - if (info != nullptr) + if (ncolors <= 1 && ncanvases <= 1) { - if (attachmentcount == 0) - throw love::Exception("Use captureScreenshot to capture the main screen's contents."); + if (colors[0].enabled) + clear(colors[0].c); - if (sX < 0 || sY < 0 || sW <= 0 || sH <= 0 || (sX + sW) > getPassWidth() || (sY + sH) > getPassHeight()) - throw love::Exception("Invalid rectangle dimensions."); - - auto imagemodule = Module::getInstance(M_IMAGE); - - if (imagemodule == nullptr) - throw love::Exception("The love.image module must be loaded to capture a Canvas' contents."); - - PixelFormat format; - switch (attachments[0].canvas->getPixelFormat()) - { - case PIXELFORMAT_RGB10A2: // FIXME: Conversions aren't supported in GLES - format = PIXELFORMAT_RGBA16; - break; - case PIXELFORMAT_R16F: - case PIXELFORMAT_RG16F: - case PIXELFORMAT_RGBA16F: - case PIXELFORMAT_RG11B10F: // FIXME: Conversions aren't supported in GLES - format = PIXELFORMAT_RGBA16F; - break; - case PIXELFORMAT_R32F: - case PIXELFORMAT_RG32F: - case PIXELFORMAT_RGBA32F: - format = PIXELFORMAT_RGBA32F; - break; - default: - format = PIXELFORMAT_RGBA8; - break; - } - - imagedata.set(imagemodule->newImageData(sW, sH, format), Acquire::NORETAIN); + return; } - if (currentPass.info.stencil) - discard(OpenGL::FRAMEBUFFER_ALL, {}, true); + bool drawbuffersmodified = false; - if (attachmentcount > 0 && attachments[0].canvas->getMSAA() > 1) + for (int i = 0; i < ncolors; i++) { - int w = attachments[0].canvas->getWidth(); - int h = attachments[0].canvas->getHeight(); + if (!colors[i].enabled) + continue; - for (int i = 0; i < attachmentcount; i++) + Colorf c = colors[i].c; + gammaCorrectColor(c); + + if (GLAD_ES_VERSION_3_0 || GLAD_VERSION_3_0) { - glReadBuffer(GL_COLOR_ATTACHMENT0 + i); + const GLfloat carray[] = {c.r, c.g, c.b, c.a}; + glClearBufferfv(GL_COLOR, i, carray); + } + else + { + glDrawBuffer(GL_COLOR_ATTACHMENT0 + i); + glClearColor(c.r, c.g, c.b, c.a); + glClear(GL_COLOR_BUFFER_BIT); - gl.bindFramebuffer(OpenGL::FRAMEBUFFER_DRAW, attachments[i].canvas->getFBO()); - - if (GLAD_APPLE_framebuffer_multisample) - glResolveMultisampleFramebufferAPPLE(); - else - glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_NEAREST); + drawbuffersmodified = true; } } - if (info != nullptr) + // Revert to the expected draw buffers once we're done, if glClearBuffer + // wasn't supported. + if (drawbuffersmodified) { - if (attachments[0].canvas->getMSAA() > 1) - gl.bindFramebuffer(OpenGL::FRAMEBUFFER_READ, attachments[0].canvas->getFBO()); - else if (attachmentcount > 1) - glReadBuffer(GL_COLOR_ATTACHMENT0); + GLenum bufs[MAX_COLOR_RENDER_TARGETS]; - GLenum datatype; - switch (imagedata->getFormat()) - { - case PIXELFORMAT_RGBA16: - datatype = GL_UNSIGNED_SHORT; - break; - case PIXELFORMAT_RGBA16F: - datatype = GL_HALF_FLOAT; - break; - case PIXELFORMAT_RGBA32F: - datatype = GL_FLOAT; - break; - default: - datatype = GL_UNSIGNED_BYTE; - break; - } + for (int i = 0; i < ncanvases; i++) + bufs[i] = GL_COLOR_ATTACHMENT0 + i; - glReadPixels(sX, sY, sW, sH, GL_RGBA, datatype, imagedata->getData()); - - info->callback(imagedata, info->ref, screenshotCallbackData); + glDrawBuffers(ncanvases, bufs); } - for (int i = 0; i < currentPass.info.colorAttachmentCount; i++) - currentPass.info.colorAttachments[i].canvas->release(); + glClear(GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - currentPass.active = false; + if (gl.bugs.clearRequiresDriverTextureStateUpdate && Shader::current) + { + // This seems to be enough to fix the bug for me. Other methods I've + // tried (e.g. dummy draws) don't work in all cases. + gl.useProgram(0); + gl.useProgram(Shader::current->getProgram()); + } } -const PassInfo &Graphics::getActivePass() const +bool Graphics::isCanvasActive() const { - return currentPass.info; + return !states.back().canvases.empty(); } -bool Graphics::isPassActive() const +bool Graphics::isCanvasActive(Canvas *canvas) const { - return currentPass.active; + for (const auto &c : states.back().canvases) + { + if (c.get() == canvas) + return true; + } + + return false; +} + +void Graphics::discard(const std::vector &colorbuffers, bool depthstencil) +{ + discard(OpenGL::FRAMEBUFFER_ALL, colorbuffers, depthstencil); } void Graphics::discard(OpenGL::FramebufferTarget target, const std::vector &colorbuffers, bool depthstencil) @@ -745,7 +730,7 @@ void Graphics::discard(OpenGL::FramebufferTarget target, const std::vector attachments.reserve(colorbuffers.size()); // glDiscardFramebuffer uses different attachment enums for the default FBO. - if (currentPass.info.colorAttachmentCount == 0 && gl.getDefaultFBO() == 0) + if (states.back().canvases.empty() && gl.getDefaultFBO() == 0) { if (colorbuffers.size() > 0 && colorbuffers[0]) attachments.push_back(GL_COLOR); @@ -758,7 +743,7 @@ void Graphics::discard(OpenGL::FramebufferTarget target, const std::vector } else { - int rendertargetcount = std::max(currentPass.info.colorAttachmentCount, 1); + int rendertargetcount = std::max((int) states.back().canvases.size(), 1); for (int i = 0; i < (int) colorbuffers.size(); i++) { @@ -780,19 +765,11 @@ void Graphics::discard(OpenGL::FramebufferTarget target, const std::vector glDiscardFramebufferEXT(gltarget, (GLint) attachments.size(), &attachments[0]); } -void Graphics::bindCachedFBOForPass(const PassInfo &pass) +void Graphics::bindCachedFBO(const std::vector &canvases) { - PassBufferInfo info; - memset(&info, 0, sizeof(PassBufferInfo)); + int ncanvases = (int) canvases.size(); - info.stencil = pass.stencil; - - int ncanvases = pass.colorAttachmentCount; - - for (int i = 0; i < ncanvases; i++) - info.canvases[i] = pass.colorAttachments[i].canvas; - - uint32 hash = XXH32(&info, offsetof(PassBufferInfo, canvases) + ncanvases * sizeof(Canvas *), 0); + uint32 hash = XXH32(&canvases[0], sizeof(Canvas *) * ncanvases, 0); GLuint fbo = framebufferObjects[hash]; @@ -802,36 +779,35 @@ void Graphics::bindCachedFBOForPass(const PassInfo &pass) } else { - int w = info.canvases[0]->getWidth(); - int h = info.canvases[0]->getHeight(); - int msaa = std::max(info.canvases[0]->getMSAA(), 1); + int w = canvases[0]->getWidth(); + int h = canvases[0]->getHeight(); + int msaa = std::max(canvases[0]->getMSAA(), 1); glGenFramebuffers(1, &fbo); gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, fbo); - std::vector drawbuffers; - drawbuffers.reserve(ncanvases); + GLenum drawbuffers[MAX_COLOR_RENDER_TARGETS]; for (int i = 0; i < ncanvases; i++) { - drawbuffers.push_back(GL_COLOR_ATTACHMENT0 + i); + drawbuffers[i] = GL_COLOR_ATTACHMENT0 + i; if (msaa > 1) { - GLuint rbo = (GLuint) info.canvases[i]->getMSAAHandle(); + GLuint rbo = (GLuint) canvases[i]->getMSAAHandle(); glFramebufferRenderbuffer(GL_FRAMEBUFFER, drawbuffers[i], GL_RENDERBUFFER, rbo); } else { - GLuint tex = *(GLuint *) info.canvases[i]->getHandle(); + GLuint tex = *(GLuint *) canvases[i]->getHandle(); glFramebufferTexture2D(GL_FRAMEBUFFER, drawbuffers[i], GL_TEXTURE_2D, tex, 0); } } - if (drawbuffers.size() > 1) - glDrawBuffers((int) drawbuffers.size(), &drawbuffers[0]); + if (ncanvases > 1) + glDrawBuffers(ncanvases, drawbuffers); - GLuint stencil = attachCachedStencilBuffer(w, h, info.canvases[0]->getRequestedMSAA()); + GLuint stencil = attachCachedStencilBuffer(w, h, canvases[0]->getRequestedMSAA()); if (stencil == 0) { @@ -872,7 +848,7 @@ GLuint Graphics::attachCachedStencilBuffer(int w, int h, int samples) } } - OpenGL::TempDebugGroup debuggroup("Created cached stencil buffer"); + OpenGL::TempDebugGroup debuggroup("Create cached stencil buffer"); CachedRenderbuffer rb; rb.w = w; @@ -921,16 +897,16 @@ GLuint Graphics::attachCachedStencilBuffer(int w, int h, int samples) } if (rb.renderbuffer != 0) + { + glClear(GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); stencilBuffers.push_back(rb); + } return rb.renderbuffer; } void Graphics::captureScreenshot(const ScreenshotInfo &info) { - if (!canCaptureScreenshot) - throw love::Exception("captureScreenshot cannot be called once rendering to the main screen has begun."); - pendingScreenshotCallbacks.push_back(info); } @@ -939,8 +915,10 @@ void Graphics::present(void *screenshotCallbackData) if (!isActive()) return; - if (currentPass.active) - throw love::Exception("present cannot be called while a render pass is active."); + if (!states.back().canvases.empty()) + throw love::Exception("present cannot be called while a Canvas is active."); + + endPass(); gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, gl.getDefaultFBO()); @@ -1049,9 +1027,7 @@ void Graphics::present(void *screenshotCallbackData) // Reset the per-frame stat counts. gl.stats.drawCalls = 0; gl.stats.shaderSwitches = 0; - renderPassCount = 0; - - canCaptureScreenshot = true; + canvasSwitchCount = 0; } int Graphics::getWidth() const @@ -1064,22 +1040,6 @@ int Graphics::getHeight() const return height; } -int Graphics::getPassWidth() const -{ - if (currentPass.active && currentPass.info.colorAttachmentCount > 0) - return currentPass.info.colorAttachments[0].canvas->getWidth(); - else - return width; -} - -int Graphics::getPassHeight() const -{ - if (currentPass.active && currentPass.info.colorAttachmentCount > 0) - return currentPass.info.colorAttachments[0].canvas->getHeight(); - else - return height; -} - bool Graphics::isCreated() const { return created; @@ -1087,12 +1047,14 @@ bool Graphics::isCreated() const void Graphics::setScissor(const Rect &rect) { + DisplayState &state = states.back(); + glEnable(GL_SCISSOR_TEST); // OpenGL's reversed y-coordinate is compensated for in OpenGL::setScissor. - gl.setScissor({rect.x, rect.y, rect.w, rect.h}, currentPass.info.colorAttachmentCount > 0); + gl.setScissor(rect, !state.canvases.empty()); - states.back().scissor = true; - states.back().scissorRect = rect; + state.scissor = true; + state.scissorRect = rect; } void Graphics::intersectScissor(const Rect &rect) @@ -1132,9 +1094,6 @@ bool Graphics::getScissor(Rect &rect) const void Graphics::drawToStencilBuffer(StencilAction action, int value) { - if (!currentPass.active || !currentPass.info.stencil) - throw love::Exception("Stenciling must be enabled in the active render pass."); - writingToStencil = true; // Disable color writes but don't save the state for it. @@ -1189,9 +1148,6 @@ void Graphics::stopDrawToStencilBuffer() void Graphics::setStencilTest(CompareMode compare, int value) { - if (!currentPass.info.stencil && compare != COMPARE_ALWAYS) - throw love::Exception("Stenciling must be enabled in the active render pass."); - DisplayState &state = states.back(); state.stencilCompare = compare; state.stencilTestValue = value; @@ -1608,25 +1564,16 @@ bool Graphics::isWireframe() const void Graphics::draw(Drawable *drawable, const Matrix4 &m) { - if (!currentPass.active) - throw RenderOutsidePassException(); - drawable->draw(m); } void Graphics::drawq(Texture *texture, Quad *quad, const Matrix4 &m) { - if (!currentPass.active) - throw RenderOutsidePassException(); - texture->drawq(quad, m); } void Graphics::print(const std::vector &str, const Matrix4 &m) { - if (!currentPass.active) - throw RenderOutsidePassException(); - checkSetDefaultFont(); DisplayState &state = states.back(); @@ -1637,9 +1584,6 @@ void Graphics::print(const std::vector &str, const Matrix4 void Graphics::printf(const std::vector &str, float wrap, Font::AlignMode align, const Matrix4 &m) { - if (!currentPass.active) - throw RenderOutsidePassException(); - checkSetDefaultFont(); DisplayState &state = states.back(); @@ -1654,9 +1598,6 @@ void Graphics::printf(const std::vector &str, float wrap, F void Graphics::points(const float *coords, const uint8 *colors, size_t numpoints) { - if (!currentPass.active) - throw RenderOutsidePassException(); - OpenGL::TempDebugGroup debuggroup("Graphics points draw"); gl.prepareDraw(); @@ -1678,9 +1619,6 @@ void Graphics::points(const float *coords, const uint8 *colors, size_t numpoints void Graphics::polyline(const float *coords, size_t count) { - if (!currentPass.active) - throw RenderOutsidePassException(); - const DisplayState &state = states.back(); float pixelsize = 1.0f / std::max((float) pixelScaleStack.back(), 0.000001f); @@ -1712,9 +1650,6 @@ void Graphics::rectangle(DrawMode mode, float x, float y, float w, float h) void Graphics::rectangle(DrawMode mode, float x, float y, float w, float h, float rx, float ry, int points) { - if (!currentPass.active) - throw RenderOutsidePassException(); - if (rx == 0 || ry == 0) { rectangle(mode, x, y, w, h); @@ -1798,9 +1733,6 @@ void Graphics::circle(DrawMode mode, float x, float y, float radius) void Graphics::ellipse(DrawMode mode, float x, float y, float a, float b, int points) { - if (!currentPass.active) - throw RenderOutsidePassException(); - float two_pi = static_cast(LOVE_M_PI * 2); if (points <= 0) points = 1; float angle_shift = (two_pi / points); @@ -1828,9 +1760,6 @@ void Graphics::ellipse(DrawMode mode, float x, float y, float a, float b) void Graphics::arc(DrawMode drawmode, ArcMode arcmode, float x, float y, float radius, float angle1, float angle2, int points) { - if (!currentPass.active) - throw RenderOutsidePassException(); - // Nothing to display with no points or equal angles. (Or is there with line mode?) if (points <= 0 || angle1 == angle2) return; @@ -1924,9 +1853,6 @@ void Graphics::arc(DrawMode drawmode, ArcMode arcmode, float x, float y, float r /// @param count the number of coordinates/size of the array void Graphics::polygon(DrawMode mode, const float *coords, size_t count) { - if (!currentPass.active) - throw RenderOutsidePassException(); - // coords is an array of a closed loop of vertices, i.e. // coords[count-2] = coords[0], coords[count-1] = coords[1] if (mode == DRAW_LINE) @@ -1981,7 +1907,7 @@ Graphics::Stats Graphics::getStats() const Stats stats; stats.drawCalls = gl.stats.drawCalls; - stats.renderPasses = renderPassCount; + stats.canvasSwitches = canvasSwitchCount; stats.shaderSwitches = gl.stats.shaderSwitches; stats.canvases = Canvas::canvasCount; stats.images = Image::imageCount; diff --git a/src/modules/graphics/opengl/Graphics.h b/src/modules/graphics/opengl/Graphics.h index 6bb409804..8f1886fc6 100644 --- a/src/modules/graphics/opengl/Graphics.h +++ b/src/modules/graphics/opengl/Graphics.h @@ -62,45 +62,6 @@ namespace graphics namespace opengl { -struct PassInfo -{ - enum BeginAction - { - BEGIN_LOAD, - BEGIN_CLEAR, - BEGIN_DISCARD, - }; - - enum EndAction - { - END_STORE, - END_DISCARD, - }; - - struct ColorAttachment - { - Canvas *canvas = nullptr; - Colorf clearColor = Colorf(0.0f, 0.0f, 0.0f, 0.0f); - BeginAction beginAction = BEGIN_LOAD; - }; - - ColorAttachment colorAttachments[MAX_COLOR_RENDER_TARGETS]; - int colorAttachmentCount = 0; - - bool stencil = false; - - bool addColorAttachment(const ColorAttachment &attachment) - { - if (colorAttachmentCount + 1 < MAX_COLOR_RENDER_TARGETS) - { - colorAttachments[colorAttachmentCount++] = attachment; - return true; - } - - return false; - } -}; - class Graphics : public love::graphics::Graphics { public: @@ -135,14 +96,13 @@ public: **/ void reset(); - void beginPass(PassInfo::BeginAction beginAction, Colorf clearColor); - void beginPass(const PassInfo &info); + void clear(Colorf color); + void clear(const std::vector &colors); - void endPass(); - void endPass(int sX, int sY, int sW, int sH, const ScreenshotInfo *info, void *screenshotCallbackData); + void discard(const std::vector &colorbuffers, bool depthstencil); - const PassInfo &getActivePass() const; - virtual bool isPassActive() const; + virtual bool isCanvasActive() const; + bool isCanvasActive(Canvas *canvas) const; /** * Flips buffers. (Rendered geometry is presented on screen). @@ -159,14 +119,18 @@ public: **/ int getHeight() const; - int getPassWidth() const; - int getPassHeight() const; - /** * True if a graphics viewport is set. **/ bool isCreated() const; + void setCanvas(Canvas *canvas); + void setCanvas(const std::vector &canvases); + void setCanvas(const std::vector> &canvases); + void setCanvas(); + + std::vector getCanvas() const; + /** * Scissor defines a box such that everything outside that box is discarded and not drawn. * Scissoring is automatically enabled. @@ -519,6 +483,8 @@ private: StrongRef font; StrongRef shader; + std::vector> canvases; + ColorMask colorMask = ColorMask(true, true, true, true); bool wireframe = false; @@ -529,18 +495,6 @@ private: float defaultMipmapSharpness = 0.0f; }; - struct CurrentPass - { - PassInfo info; - bool active = false; - }; - - struct PassBufferInfo - { - bool stencil; - Canvas *canvases[MAX_COLOR_RENDER_TARGETS]; - }; - struct CachedRenderbuffer { int w; @@ -553,7 +507,8 @@ private: void restoreState(const DisplayState &s); void restoreStateChecked(const DisplayState &s); - void bindCachedFBOForPass(const PassInfo &pass); + void endPass(); + void bindCachedFBO(const std::vector &canvases); void discard(OpenGL::FramebufferTarget target, const std::vector &colorbuffers, bool depthstencil); GLuint attachCachedStencilBuffer(int w, int h, int samples); @@ -577,16 +532,12 @@ private: bool created; bool active; - bool canCaptureScreenshot; - - CurrentPass currentPass; - bool writingToStencil; std::vector states; std::vector stackTypes; // Keeps track of the pushed stack types. - int renderPassCount; + int canvasSwitchCount; static const size_t MAX_USER_STACK_DEPTH = 64; diff --git a/src/modules/graphics/opengl/Shader.cpp b/src/modules/graphics/opengl/Shader.cpp index 2e4293492..a0815f03d 100644 --- a/src/modules/graphics/opengl/Shader.cpp +++ b/src/modules/graphics/opengl/Shader.cpp @@ -806,7 +806,7 @@ void Shader::checkSetScreenParams() Rect view = gl.getViewport(); auto gfx = Module::getInstance(Module::M_GRAPHICS); - bool canvasActive = gfx->getActivePass().colorAttachmentCount > 0; + bool canvasActive = gfx->isCanvasActive(); if (view == lastViewport && canvasWasActive == canvasActive) return; diff --git a/src/modules/graphics/opengl/wrap_Canvas.cpp b/src/modules/graphics/opengl/wrap_Canvas.cpp index ff47f746a..08694792c 100644 --- a/src/modules/graphics/opengl/wrap_Canvas.cpp +++ b/src/modules/graphics/opengl/wrap_Canvas.cpp @@ -52,10 +52,61 @@ int w_Canvas_getMSAA(lua_State *L) return 1; } +int w_Canvas_renderTo(lua_State *L) +{ + Canvas *canvas = luax_checkcanvas(L, 1); + luaL_checktype(L, 2, LUA_TFUNCTION); + + auto graphics = Module::getInstance(Module::M_GRAPHICS); + + if (graphics) + { + // Save the current Canvas so we can restore it when we're done. + std::vector oldcanvases = graphics->getCanvas(); + + for (Canvas *c : oldcanvases) + c->retain(); + + luax_catchexcept(L, [&](){ graphics->setCanvas(canvas); }); + + lua_settop(L, 2); // make sure the function is on top of the stack + int status = lua_pcall(L, 0, 0, 0); + + graphics->setCanvas(oldcanvases); + + for (Canvas *c : oldcanvases) + c->release(); + + if (status != 0) + return lua_error(L); + } + + return 0; +} + +int w_Canvas_newImageData(lua_State *L) +{ + Canvas *canvas = luax_checkcanvas(L, 1); + love::image::Image *image = luax_getmodule(L, love::image::Image::type); + int x = (int) luaL_optnumber(L, 2, 0); + int y = (int) luaL_optnumber(L, 3, 0); + int w = (int) luaL_optnumber(L, 4, canvas->getWidth()); + int h = (int) luaL_optnumber(L, 5, canvas->getHeight()); + + love::image::ImageData *img = nullptr; + luax_catchexcept(L, [&](){ img = canvas->newImageData(image, x, y, w, h); }); + + luax_pushtype(L, img); + img->release(); + return 1; +} + static const luaL_Reg w_Canvas_functions[] = { { "getFormat", w_Canvas_getFormat }, { "getMSAA", w_Canvas_getMSAA }, + { "renderTo", w_Canvas_renderTo }, + { "newImageData", w_Canvas_newImageData }, { 0, 0 } }; diff --git a/src/modules/graphics/opengl/wrap_Graphics.cpp b/src/modules/graphics/opengl/wrap_Graphics.cpp index d606a47ee..3c0f022ea 100644 --- a/src/modules/graphics/opengl/wrap_Graphics.cpp +++ b/src/modules/graphics/opengl/wrap_Graphics.cpp @@ -62,6 +62,76 @@ int w_reset(lua_State *) return 0; } +int w_clear(lua_State *L) +{ + Colorf color; + + if (lua_isnoneornil(L, 1)) + color.set(0.0, 0.0, 0.0, 0.0); + else if (lua_istable(L, 1)) + { + std::vector colors((size_t) lua_gettop(L)); + + for (int i = 0; i < lua_gettop(L); i++) + { + if (lua_isnoneornil(L, i + 1) || luax_objlen(L, i + 1) == 0) + { + colors[i].enabled = false; + continue; + } + + for (int j = 1; j <= 4; j++) + lua_rawgeti(L, i + 1, j); + + colors[i].enabled = true; + colors[i].c.r = (float) luaL_checknumber(L, -4); + colors[i].c.g = (float) luaL_checknumber(L, -3); + colors[i].c.b = (float) luaL_checknumber(L, -2); + colors[i].c.a = (float) luaL_optnumber(L, -1, 1.0); + + lua_pop(L, 4); + } + + luax_catchexcept(L, [&]() { instance()->clear(colors); }); + return 0; + } + else + { + color.r = (float) luaL_checknumber(L, 1); + color.g = (float) luaL_checknumber(L, 2); + color.b = (float) luaL_checknumber(L, 3); + color.a = (float) luaL_optnumber(L, 4, 1.0); + } + + luax_catchexcept(L, [&]() { instance()->clear(color); }); + return 0; +} + +int w_discard(lua_State *L) +{ + std::vector colorbuffers; + + if (lua_istable(L, 1)) + { + for (size_t i = 1; i <= luax_objlen(L, 1); i++) + { + lua_rawgeti(L, 1, i); + colorbuffers.push_back(luax_optboolean(L, -1, true)); + lua_pop(L, 1); + } + } + else + { + bool discardcolor = luax_optboolean(L, 1, true); + size_t numbuffers = std::max((size_t) 1, instance()->getCanvas().size()); + colorbuffers = std::vector(numbuffers, discardcolor); + } + + bool stencil = luax_optboolean(L, 2, true); + instance()->discard(colorbuffers, stencil); + return 0; +} + int w_present(lua_State *L) { luax_catchexcept(L, [&]() { instance()->present(L); }); @@ -105,149 +175,64 @@ int w_getDimensions(lua_State *L) return 2; } -int w_getPassWidth(lua_State *L) +int w_setCanvas(lua_State *L) { - lua_pushinteger(L, instance()->getPassWidth()); - return 1; -} - -int w_getPassHeight(lua_State *L) -{ - lua_pushinteger(L, instance()->getPassHeight()); - return 1; -} - -int w_getPassDimensions(lua_State *L) -{ - lua_pushinteger(L, instance()->getPassWidth()); - lua_pushinteger(L, instance()->getPassHeight()); - return 2; -} - -static int w__beginPass(lua_State *L) -{ - int nextstartidx = 1; + // Disable stencil writes. + instance()->stopDrawToStencilBuffer(); + // called with none -> reset to default buffer if (lua_isnoneornil(L, 1)) { - luax_catchexcept(L, [&]() { instance()->beginPass(PassInfo::BEGIN_LOAD, Colorf()); }); - nextstartidx = 1; + instance()->setCanvas(); + return 0; } - else if (lua_isnumber(L, 1)) + + bool is_table = lua_istable(L, 1); + std::vector canvases; + + if (is_table) { - Colorf c; - c.r = (float) luaL_checknumber(L, 1); - c.g = (float) luaL_checknumber(L, 2); - c.b = (float) luaL_checknumber(L, 3); - - if (lua_isnumber(L, 4)) + for (int i = 1; i <= (int) luax_objlen(L, 1); i++) { - c.a = (float) lua_tonumber(L, 4); - nextstartidx = 5; + lua_rawgeti(L, 1, i); + canvases.push_back(luax_checkcanvas(L, -1)); + lua_pop(L, 1); } - else - { - c.a = 1.0f; - nextstartidx = 4; - } - - luax_catchexcept(L, [&]() { instance()->beginPass(PassInfo::BEGIN_CLEAR, c); }); - } - else if (luax_istype(L, 1, Canvas::type)) - { - PassInfo::ColorAttachment attachment; - attachment.canvas = luax_checkcanvas(L, 1); - attachment.beginAction = PassInfo::BEGIN_LOAD; - - if (lua_isnumber(L, 2)) - { - attachment.beginAction = PassInfo::BEGIN_CLEAR; - attachment.clearColor.r = (float) luaL_checknumber(L, 2); - attachment.clearColor.g = (float) luaL_checknumber(L, 3); - attachment.clearColor.b = (float) luaL_checknumber(L, 4); - - if (lua_isnumber(L, 5)) - { - attachment.clearColor.a = (float) lua_tonumber(L, 5); - nextstartidx = 6; - } - else - { - attachment.clearColor.a = 1.0f; - nextstartidx = 5; - } - } - else - nextstartidx = 2; - - PassInfo info; - info.addColorAttachment(attachment); - - if (lua_isboolean(L, nextstartidx)) - { - info.stencil = luax_toboolean(L, nextstartidx); - nextstartidx++; - } - else - info.stencil = false; - - luax_catchexcept(L, [&]() { instance()->beginPass(info); }); } else { - luaL_checktype(L, 1, LUA_TTABLE); - - int nattachments = std::max((int) luax_objlen(L, 1), 1); - - if (nattachments > MAX_COLOR_RENDER_TARGETS) - return luaL_error(L, "Cannot render to %d Canvases at once!", nattachments); - - PassInfo info; - - for (int i = 1; i <= nattachments; i++) - { - lua_rawgeti(L, 1, i); - luaL_checktype(L, -1, LUA_TTABLE); - - PassInfo::ColorAttachment attachment; - attachment.beginAction = PassInfo::BEGIN_LOAD; - - lua_rawgeti(L, -1, 1); - attachment.canvas = luax_checkcanvas(L, -1); - - lua_rawgeti(L, -2, 2); - if (!lua_isnoneornil(L, -1)) - { - attachment.beginAction = PassInfo::BEGIN_CLEAR; - - for (int j = 3; j < 6; j++) - lua_rawgeti(L, -j, j); - - attachment.clearColor.r = (float) luaL_checknumber(L, -4); - attachment.clearColor.g = (float) luaL_checknumber(L, -3); - attachment.clearColor.b = (float) luaL_checknumber(L, -2); - attachment.clearColor.a = (float) luaL_optnumber(L, -1, 1.0); - } - - lua_pop(L, 2 + (attachment.beginAction == PassInfo::BEGIN_CLEAR ? 4 : 1)); - - info.addColorAttachment(attachment); - } - - info.stencil = luax_boolflag(L, 1, "stencil", false); - - luax_catchexcept(L, [&]() { instance()->beginPass(info); }); - - nextstartidx = 2; + for (int i = 1; i <= lua_gettop(L); i++) + canvases.push_back(luax_checkcanvas(L, i)); } - return nextstartidx; + luax_catchexcept(L, [&]() { + if (canvases.size() > 0) + instance()->setCanvas(canvases); + else + instance()->setCanvas(); + }); + + return 0; } -int w_beginPass(lua_State *L) +int w_getCanvas(lua_State *L) { - w__beginPass(L); - return 0; + const std::vector canvases = instance()->getCanvas(); + int n = 0; + + for (Canvas *c : canvases) + { + luax_pushtype(L, c); + n++; + } + + if (n == 0) + { + lua_pushnil(L); + n = 1; + } + + return n; } static void screenshotCallback(love::image::ImageData *i, Reference *ref, void *gd) @@ -264,96 +249,6 @@ static void screenshotCallback(love::image::ImageData *i, Reference *ref, void * delete ref; } -static int w__endPass(lua_State *L, int startidx) -{ - if (lua_isnoneornil(L, startidx)) - { - luax_catchexcept(L, []() { instance()->endPass(); }); - } - else - { - int x, y, w, h; - - if (lua_isnumber(L, startidx)) - { - x = (int) luaL_checknumber(L, 1); - y = (int) luaL_checknumber(L, 2); - w = (int) luaL_checknumber(L, 3); - h = (int) luaL_checknumber(L, 4); - startidx += 4; - } - else - { - x = 0; - y = 0; - w = instance()->getPassWidth(); - h = instance()->getPassHeight(); - } - - luaL_checktype(L, startidx, LUA_TFUNCTION); - - Graphics::ScreenshotInfo info; - info.callback = screenshotCallback; - - lua_pushvalue(L, startidx); - info.ref = luax_refif(L, LUA_TFUNCTION); - lua_pop(L, 1); - - luax_catchexcept(L, - [&]() { instance()->endPass(x, y, w, h, &info, L); }, - [&](bool except) { if (except) delete info.ref; } - ); - } - - return 0; -} - -int w_endPass(lua_State *L) -{ - return w__endPass(L, 1); -} - -int w_renderPass(lua_State *L) -{ - int startidx = w__beginPass(L); - - if (lua_type(L, startidx) != LUA_TFUNCTION) - { - w__endPass(L, startidx + 1); - luaL_checktype(L, startidx, LUA_TFUNCTION); - return 0; - } - - int nargs = lua_gettop(L) - startidx; - int status = lua_pcall(L, nargs, 0, 0); - - w__endPass(L, startidx + 1); - - if (status != 0) - return lua_error(L); - - return 0; -} - -int w_isPassActive(lua_State *L) -{ - luax_pushboolean(L, instance()->isPassActive()); - return 1; -} - -int w_getPassCanvases(lua_State *L) -{ - if (!instance()->isPassActive()) - return 0; - - const PassInfo &info = instance()->getActivePass(); - - for (const auto &attachment : info.colorAttachments) - luax_pushtype(L, attachment.canvas); - - return info.colorAttachmentCount; -} - int w_captureScreenshot(lua_State *L) { luaL_checktype(L, 1, LUA_TFUNCTION); @@ -1600,8 +1495,8 @@ int w_getStats(lua_State *L) lua_pushinteger(L, stats.drawCalls); lua_setfield(L, -2, "drawcalls"); - lua_pushinteger(L, stats.renderPasses); - lua_setfield(L, -2, "renderpasses"); + lua_pushinteger(L, stats.canvasSwitches); + lua_setfield(L, -2, "canvasswitches"); lua_pushinteger(L, stats.shaderSwitches); lua_setfield(L, -2, "shaderswitches"); @@ -2142,6 +2037,8 @@ int w_inverseTransformPoint(lua_State *L) static const luaL_Reg functions[] = { { "reset", w_reset }, + { "clear", w_clear }, + { "discard", w_discard }, { "present", w_present }, { "newImage", w_newImage }, @@ -2156,6 +2053,9 @@ static const luaL_Reg functions[] = { "newText", w_newText }, { "_newVideo", w_newVideo }, + { "setCanvas", w_setCanvas }, + { "getCanvas", w_getCanvas }, + { "setColor", w_setColor }, { "getColor", w_getColor }, { "setBackgroundColor", w_setBackgroundColor }, @@ -2195,12 +2095,6 @@ static const luaL_Reg functions[] = { "getSystemLimits", w_getSystemLimits }, { "getStats", w_getStats }, - { "beginPass", w_beginPass }, - { "endPass", w_endPass }, - { "renderPass", w_renderPass }, - { "isPassActive", w_isPassActive }, - { "getPassCanvases", w_getPassCanvases }, - { "captureScreenshot", w_captureScreenshot }, { "draw", w_draw }, @@ -2214,9 +2108,6 @@ static const luaL_Reg functions[] = { "getWidth", w_getWidth }, { "getHeight", w_getHeight }, { "getDimensions", w_getDimensions }, - { "getPassWidth", w_getPassWidth }, - { "getPassHeight", w_getPassHeight }, - { "getPassDimensions", w_getPassDimensions }, { "setScissor", w_setScissor }, { "intersectScissor", w_intersectScissor }, diff --git a/src/modules/window/sdl/Window.cpp b/src/modules/window/sdl/Window.cpp index 12c3ebe19..86b312084 100644 --- a/src/modules/window/sdl/Window.cpp +++ b/src/modules/window/sdl/Window.cpp @@ -411,8 +411,8 @@ bool Window::setWindow(int width, int height, WindowSettings *settings) if (!graphics.get()) graphics.set(Module::getInstance(Module::M_GRAPHICS)); - if (graphics.get() && graphics->isPassActive()) - throw love::Exception("setMode cannot be called while a render pass is active in love.graphics."); + if (graphics.get() && graphics->isCanvasActive()) + throw love::Exception("setMode cannot be called while a Canvas is active in love.graphics."); WindowSettings f; @@ -627,8 +627,8 @@ void Window::close() { if (graphics.get()) { - if (graphics->isPassActive()) - throw love::Exception("close cannot be called while a render pass is active in love.graphics."); + if (graphics->isCanvasActive()) + throw love::Exception("close cannot be called while a Canvas is active in love.graphics."); graphics->unSetMode(); } @@ -657,8 +657,8 @@ bool Window::setFullscreen(bool fullscreen, Window::FullscreenType fstype) if (!window) return false; - if (graphics.get() && graphics->isPassActive()) - throw love::Exception("setFullscreen cannot be called while a render pass is active in love.graphics."); + if (graphics.get() && graphics->isCanvasActive()) + throw love::Exception("setFullscreen cannot be called while a Canvas is active in love.graphics."); WindowSettings newsettings = settings; newsettings.fullscreen = fullscreen; diff --git a/src/scripts/boot.lua b/src/scripts/boot.lua index cd9d19d39..1a9061093 100644 --- a/src/scripts/boot.lua +++ b/src/scripts/boot.lua @@ -547,12 +547,9 @@ function love.run() if love.graphics and love.graphics.isActive() then love.graphics.origin() + love.graphics.clear(love.graphics.getBackgroundColor()) - if love.drawpasses then love.drawpasses() end - - love.graphics.beginPass(love.graphics.getBackgroundColor()) if love.draw then love.draw() end - love.graphics.endPass() love.graphics.present() end @@ -606,9 +603,6 @@ function love.errhand(msg) if love.audio then love.audio.stop() end love.graphics.reset() - if love.graphics.isPassActive() then - love.graphics.endPass() - end local font = love.graphics.setNewFont(math.floor(love.window.toPixels(14))) @@ -636,8 +630,10 @@ function love.errhand(msg) p = string.gsub(p, "%[string \"(.-)\"%]", "%1") local function draw() + love.graphics.clear(89/255, 157/255, 220/255) local pos = love.window.toPixels(70) love.graphics.printf(p, pos, pos, love.graphics.getWidth() - pos) + love.graphics.present() end while true do @@ -659,8 +655,7 @@ function love.errhand(msg) end end - love.graphics.renderPass(89/255, 157/255, 220/255, draw) - love.graphics.present() + draw() if love.timer then love.timer.sleep(0.1) diff --git a/src/scripts/boot.lua.h b/src/scripts/boot.lua.h index 32c2df831..00f450ed4 100644 --- a/src/scripts/boot.lua.h +++ b/src/scripts/boot.lua.h @@ -1010,17 +1010,12 @@ const unsigned char boot_lua[] = 0x2e, 0x69, 0x73, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x28, 0x29, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a, 0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x28, 0x29, 0x0a, - 0x09, 0x09, 0x09, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x64, 0x72, 0x61, 0x77, 0x70, 0x61, 0x73, - 0x73, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x64, 0x72, 0x61, 0x77, - 0x70, 0x61, 0x73, 0x73, 0x65, 0x73, 0x28, 0x29, 0x20, 0x65, 0x6e, 0x64, 0x0a, - 0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x62, - 0x65, 0x67, 0x69, 0x6e, 0x50, 0x61, 0x73, 0x73, 0x28, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, - 0x68, 0x69, 0x63, 0x73, 0x2e, 0x67, 0x65, 0x74, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, - 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x28, 0x29, 0x29, 0x0a, + 0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x63, + 0x6c, 0x65, 0x61, 0x72, 0x28, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, + 0x2e, 0x67, 0x65, 0x74, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6c, 0x6f, + 0x72, 0x28, 0x29, 0x29, 0x0a, 0x09, 0x09, 0x09, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x64, 0x72, 0x61, 0x77, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x64, 0x72, 0x61, 0x77, 0x28, 0x29, 0x20, 0x65, 0x6e, 0x64, 0x0a, - 0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x65, - 0x6e, 0x64, 0x50, 0x61, 0x73, 0x73, 0x28, 0x29, 0x0a, 0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x28, 0x29, 0x0a, 0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a, @@ -1106,12 +1101,6 @@ const unsigned char boot_lua[] = 0x29, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x72, 0x65, 0x73, 0x65, 0x74, 0x28, 0x29, 0x0a, - 0x09, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, - 0x69, 0x73, 0x50, 0x61, 0x73, 0x73, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x28, 0x29, 0x20, 0x74, 0x68, 0x65, - 0x6e, 0x0a, - 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x65, 0x6e, - 0x64, 0x50, 0x61, 0x73, 0x73, 0x28, 0x29, 0x0a, - 0x09, 0x65, 0x6e, 0x64, 0x0a, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x73, 0x65, 0x74, 0x4e, 0x65, 0x77, 0x46, 0x6f, 0x6e, 0x74, 0x28, 0x6d, 0x61, 0x74, 0x68, 0x2e, 0x66, 0x6c, 0x6f, 0x6f, 0x72, 0x28, 0x6c, 0x6f, 0x76, 0x65, @@ -1151,6 +1140,9 @@ const unsigned char boot_lua[] = 0x5c, 0x22, 0x25, 0x5d, 0x22, 0x2c, 0x20, 0x22, 0x25, 0x31, 0x22, 0x29, 0x0a, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x64, 0x72, 0x61, 0x77, 0x28, 0x29, 0x0a, + 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x63, 0x6c, + 0x65, 0x61, 0x72, 0x28, 0x38, 0x39, 0x2f, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x31, 0x35, 0x37, 0x2f, 0x32, 0x35, + 0x35, 0x2c, 0x20, 0x32, 0x32, 0x30, 0x2f, 0x32, 0x35, 0x35, 0x29, 0x0a, 0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x70, 0x6f, 0x73, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x74, 0x6f, 0x50, 0x69, 0x78, 0x65, 0x6c, 0x73, 0x28, 0x37, 0x30, 0x29, 0x0a, @@ -1158,6 +1150,8 @@ const unsigned char boot_lua[] = 0x69, 0x6e, 0x74, 0x66, 0x28, 0x70, 0x2c, 0x20, 0x70, 0x6f, 0x73, 0x2c, 0x20, 0x70, 0x6f, 0x73, 0x2c, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x67, 0x65, 0x74, 0x57, 0x69, 0x64, 0x74, 0x68, 0x28, 0x29, 0x20, 0x2d, 0x20, 0x70, 0x6f, 0x73, 0x29, 0x0a, + 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x70, 0x72, + 0x65, 0x73, 0x65, 0x6e, 0x74, 0x28, 0x29, 0x0a, 0x09, 0x65, 0x6e, 0x64, 0x0a, 0x09, 0x77, 0x68, 0x69, 0x6c, 0x65, 0x20, 0x74, 0x72, 0x75, 0x65, 0x20, 0x64, 0x6f, 0x0a, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x75, 0x6d, 0x70, 0x28, @@ -1194,12 +1188,7 @@ const unsigned char boot_lua[] = 0x09, 0x09, 0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a, 0x09, 0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a, 0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a, - 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x72, 0x65, - 0x6e, 0x64, 0x65, 0x72, 0x50, 0x61, 0x73, 0x73, 0x28, 0x38, 0x39, 0x2f, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x31, - 0x35, 0x37, 0x2f, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x32, 0x32, 0x30, 0x2f, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x64, - 0x72, 0x61, 0x77, 0x29, 0x0a, - 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x70, 0x72, - 0x65, 0x73, 0x65, 0x6e, 0x74, 0x28, 0x29, 0x0a, + 0x09, 0x09, 0x64, 0x72, 0x61, 0x77, 0x28, 0x29, 0x0a, 0x09, 0x09, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x72, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a, 0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x72, 0x2e, 0x73, 0x6c, 0x65, 0x65,