diff --git a/src/common/Matrix.cpp b/src/common/Matrix.cpp index 7abc80a0e..f8aa8353d 100644 --- a/src/common/Matrix.cpp +++ b/src/common/Matrix.cpp @@ -181,6 +181,32 @@ const float *Matrix4::getElements() const return e; } +void Matrix4::setRow(int r, const Vector4 &v) +{ + e[0 * 4 + r] = v.x; + e[1 * 4 + r] = v.y; + e[2 * 4 + r] = v.z; + e[3 * 4 + r] = v.w; +} + +Vector4 Matrix4::getRow(int r) const +{ + return Vector4(e[0 * 4 + r], e[1 * 4 + r], e[2 * 4 + r], e[3 * 4 + r]); +} + +void Matrix4::setColumn(int c, const Vector4 &v) +{ + e[c * 4 + 0] = v.x; + e[c * 4 + 1] = v.y; + e[c * 4 + 2] = v.z; + e[c * 4 + 3] = v.w; +} + +Vector4 Matrix4::getColumn(int c) const +{ + return Vector4(e[c * 4 + 0], e[c * 4 + 1], e[c * 4 + 2], e[c * 4 + 3]); +} + void Matrix4::setIdentity() { memset(e, 0, sizeof(float)*16); @@ -430,6 +456,23 @@ Matrix4 Matrix4::ortho(float left, float right, float bottom, float top, float n return m; } +Matrix4 Matrix4::perspective(float verticalfov, float aspect, float near, float far) +{ + Matrix4 m; + + float cotangent = 1.0f / tanf(verticalfov * 0.5f); + + m.e[0] = cotangent / aspect; + m.e[5] = cotangent; + m.e[10] = (far + near) / (near - far); + m.e[11] = -1.0f; + + m.e[14] = 2.0f * near * far / (near - far); + m.e[15] = 0.0f; + + return m; +} + /** * | e0 e3 e6 | * | e1 e4 e7 | diff --git a/src/common/Matrix.h b/src/common/Matrix.h index 127c2bec3..77586b9bd 100644 --- a/src/common/Matrix.h +++ b/src/common/Matrix.h @@ -23,6 +23,7 @@ // LOVE #include "math.h" +#include "Vector.h" namespace love { @@ -88,6 +89,11 @@ public: **/ const float *getElements() const; + void setRow(int r, const Vector4 &v); + Vector4 getRow(int r) const; + + void setColumn(int c, const Vector4 &v); + Vector4 getColumn(int c) const; /** * Resets this Matrix to the identity matrix. **/ @@ -222,6 +228,11 @@ public: **/ static Matrix4 ortho(float left, float right, float bottom, float top, float near, float far); + /** + * Creates a new perspective projection matrix. + **/ + static Matrix4 perspective(float verticalfov, float aspect, float near, float far); + private: /** diff --git a/src/modules/graphics/Graphics.cpp b/src/modules/graphics/Graphics.cpp index 38111939e..a27992dc3 100644 --- a/src/modules/graphics/Graphics.cpp +++ b/src/modules/graphics/Graphics.cpp @@ -179,7 +179,7 @@ Graphics::Graphics() , created(false) , active(true) , batchedDrawState() - , projectionMatrix() + , deviceProjectionMatrix() , renderTargetSwitchCount(0) , drawCalls(0) , drawCallsBatched(0) @@ -541,6 +541,11 @@ void Graphics::restoreState(const DisplayState &s) setWireframe(s.wireframe); setDefaultSamplerState(s.defaultSamplerState); + + if (s.useCustomProjection) + updateDeviceProjection(s.customProjection); + else + resetProjection(); } void Graphics::restoreStateChecked(const DisplayState &s) @@ -625,6 +630,11 @@ void Graphics::restoreStateChecked(const DisplayState &s) setWireframe(s.wireframe); setDefaultSamplerState(s.defaultSamplerState); + + if (s.useCustomProjection) + setCustomProjection(s.customProjection); + else if (cur.useCustomProjection) + resetProjection(); } Colorf Graphics::getColor() const @@ -878,6 +888,8 @@ void Graphics::setRenderTargets(const RenderTargets &rts) std::swap(state.renderTargets, refs); renderTargetSwitchCount++; + + resetProjection(); } void Graphics::setRenderTarget() @@ -892,6 +904,8 @@ void Graphics::setRenderTarget() state.renderTargets = RenderTargetsStrongRef(); renderTargetSwitchCount++; + + resetProjection(); } Graphics::RenderTargets Graphics::getRenderTargets() const @@ -2097,9 +2111,9 @@ const Matrix4 &Graphics::getTransform() const return transformStack.back(); } -const Matrix4 &Graphics::getProjection() const +const Matrix4 &Graphics::getDeviceProjection() const { - return projectionMatrix; + return deviceProjectionMatrix; } void Graphics::pushTransform() @@ -2179,6 +2193,81 @@ Vector2 Graphics::inverseTransformPoint(Vector2 point) return p; } +void Graphics::setOrthoProjection(float w, float h, float near, float far) +{ + if (near >= far) + throw love::Exception("Orthographic projection Z far value must be greater than the Z near value."); + + Matrix4 m = Matrix4::ortho(0.0f, w, 0.0f, h, near, far); + setCustomProjection(m); +} + +void Graphics::setPerspectiveProjection(float verticalfov, float aspect, float near, float far) +{ + if (near <= 0.0f) + throw love::Exception("Perspective projection Z near value must be greater than 0."); + + if (near >= far) + throw love::Exception("Perspective projection Z far value must be greater than the Z near value."); + + Matrix4 m = Matrix4::perspective(verticalfov, aspect, near, far); + setCustomProjection(m); +} + +void Graphics::setCustomProjection(const Matrix4 &m) +{ + flushBatchedDraws(); + + auto &state = states.back(); + + state.useCustomProjection = true; + state.customProjection = m; + + updateDeviceProjection(m); +} + +void Graphics::resetProjection() +{ + flushBatchedDraws(); + + auto &state = states.back(); + int w = getWidth(); + int h = getHeight(); + + const auto &rt = state.renderTargets.getFirstTarget(); + if (rt.texture.get()) + { + w = rt.texture->getWidth(rt.mipmap); + h = rt.texture->getHeight(rt.mipmap); + } + + state.useCustomProjection = false; + + updateDeviceProjection(Matrix4::ortho(0.0f, w, 0.0f, h, -10.0f, 10.0f)); +} + +void Graphics::updateDeviceProjection(const Matrix4 &projection) +{ + // Note: graphics implementations define computeDeviceProjection. + deviceProjectionMatrix = computeDeviceProjection(projection, isRenderTargetActive()); +} + +Matrix4 Graphics::calculateDeviceProjection(const Matrix4 &projection, uint32 flags) const +{ + Matrix4 m = projection; + bool reverseZ = (flags & DEVICE_PROJECTION_REVERSE_Z) != 0; + + if (flags & DEVICE_PROJECTION_FLIP_Y) + m.setRow(1, -m.getRow(1)); + + if (flags & DEVICE_PROJECTION_Z_01) // Go from Z [-1, 1] to Z [0, 1]. + m.setRow(2, m.getRow(2) * (reverseZ ? -0.5f : 0.5f) + m.getRow(3)); + else if (reverseZ) + m.setRow(2, -m.getRow(2)); + + return m; +} + STRINGMAP_CLASS_BEGIN(Graphics, Graphics::DrawMode, Graphics::DRAW_MAX_ENUM, drawMode) { { "line", Graphics::DRAW_LINE }, diff --git a/src/modules/graphics/Graphics.h b/src/modules/graphics/Graphics.h index 972aa7cd5..58ab4a298 100644 --- a/src/modules/graphics/Graphics.h +++ b/src/modules/graphics/Graphics.h @@ -834,7 +834,7 @@ public: void pop(); const Matrix4 &getTransform() const; - const Matrix4 &getProjection() const; + const Matrix4 &getDeviceProjection() const; void rotate(float r); void scale(float x, float y = 1.0f); @@ -848,6 +848,13 @@ public: Vector2 transformPoint(Vector2 point); Vector2 inverseTransformPoint(Vector2 point); + void setOrthoProjection(float w, float h, float near, float far); + void setPerspectiveProjection(float verticalfov, float aspect, float near, float far); + void setCustomProjection(const Matrix4 &m); + void resetProjection(); + + virtual Matrix4 computeDeviceProjection(const Matrix4 &projection, bool rendertotexture) const = 0; + virtual void draw(const DrawCommand &cmd) = 0; virtual void draw(const DrawIndexedCommand &cmd) = 0; virtual void drawQuads(int start, int count, const VertexAttributes &attributes, const BufferBindings &buffers, Texture *texture) = 0; @@ -882,6 +889,14 @@ public: protected: + enum DeviceProjectionFlags + { + DEVICE_PROJECTION_DEFAULT = 0, + DEVICE_PROJECTION_FLIP_Y = (1 << 0), + DEVICE_PROJECTION_Z_01 = (1 << 1), + DEVICE_PROJECTION_REVERSE_Z = (1 << 2), + }; + struct DisplayState { DisplayState(); @@ -917,6 +932,9 @@ protected: bool wireframe = false; + bool useCustomProjection = false; + Matrix4 customProjection; + // Default mipmap filter is set in the DisplayState constructor. SamplerState defaultSamplerState = SamplerState(); }; @@ -978,6 +996,9 @@ protected: void pushIdentityTransform(); void popTransform(); + void updateDeviceProjection(const Matrix4 &projection); + Matrix4 calculateDeviceProjection(const Matrix4 &projection, uint32 flags) const; + int width; int height; int pixelWidth; @@ -993,7 +1014,7 @@ protected: BatchedDrawState batchedDrawState; std::vector transformStack; - Matrix4 projectionMatrix; + Matrix4 deviceProjectionMatrix; std::vector pixelScaleStack; diff --git a/src/modules/graphics/opengl/Graphics.cpp b/src/modules/graphics/opengl/Graphics.cpp index c2e9532e4..38d996c40 100644 --- a/src/modules/graphics/opengl/Graphics.cpp +++ b/src/modules/graphics/opengl/Graphics.cpp @@ -177,6 +177,18 @@ love::graphics::Buffer *Graphics::newBuffer(const Buffer::Settings &settings, co return new Buffer(this, settings, format, data, size, arraylength); } +Matrix4 Graphics::computeDeviceProjection(const Matrix4 &projection, bool rendertotexture) const +{ + uint32 flags = DEVICE_PROJECTION_DEFAULT; + + // The projection matrix is flipped compared to rendering to a texture, due + // to OpenGL considering (0,0) bottom-left instead of top-left. + if (!rendertotexture) + flags |= DEVICE_PROJECTION_FLIP_Y; + + return calculateDeviceProjection(projection, flags); +} + void Graphics::setViewportSize(int width, int height, int pixelwidth, int pixelheight) { this->width = width; @@ -194,8 +206,7 @@ void Graphics::setViewportSize(int width, int height, int pixelwidth, int pixelh if (states.back().scissor) setScissor(states.back().scissorRect); - // Set up the projection matrix - projectionMatrix = Matrix4::ortho(0.0, (float) width, (float) height, 0.0, -10.0f, 10.0f); + resetProjection(); } updateBackbuffer(width, height, pixelwidth, pixelheight, requestedBackbufferMSAA); @@ -731,7 +742,7 @@ void Graphics::setDebug(bool enable) ::printf("OpenGL debug output enabled (LOVE_GRAPHICS_DEBUG=1)\n"); } -void Graphics::setRenderTargetsInternal(const RenderTargets &rts, int w, int h, int pixelw, int pixelh, bool hasSRGBtexture) +void Graphics::setRenderTargetsInternal(const RenderTargets &rts, int /*w*/, int /*h*/, int pixelw, int pixelh, bool hasSRGBtexture) { const DisplayState &state = states.back(); @@ -745,19 +756,14 @@ void Graphics::setRenderTargetsInternal(const RenderTargets &rts, int w, int h, if (iswindow) { gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, getInternalBackbufferFBO()); - - // The projection matrix is flipped compared to rendering to a texture, - // due to OpenGL considering (0,0) bottom-left instead of top-left. - projectionMatrix = Matrix4::ortho(0.0, (float) w, (float) h, 0.0, -10.0f, 10.0f); } else { bindCachedFBO(rts); - projectionMatrix = Matrix4::ortho(0.0, (float) w, 0.0, (float) h, -10.0f, 10.0f); - // Flip front face winding when rendering to a texture, since our // projection matrix is flipped. + // Note: projection matrix is set at a higher level. vertexwinding = vertexwinding == WINDING_CW ? WINDING_CCW : WINDING_CW; } diff --git a/src/modules/graphics/opengl/Graphics.h b/src/modules/graphics/opengl/Graphics.h index 89aaef820..e364ac51b 100644 --- a/src/modules/graphics/opengl/Graphics.h +++ b/src/modules/graphics/opengl/Graphics.h @@ -62,6 +62,8 @@ public: love::graphics::Texture *newTexture(const Texture::Settings &settings, const Texture::Slices *data = nullptr) override; love::graphics::Buffer *newBuffer(const Buffer::Settings &settings, const std::vector &format, const void *data, size_t size, size_t arraylength) override; + Matrix4 computeDeviceProjection(const Matrix4 &projection, bool rendertotexture) const override; + void setViewportSize(int width, int height, int pixelwidth, int pixelheight) override; bool setMode(void *context, int width, int height, int pixelwidth, int pixelheight, bool windowhasstencil, int msaa) override; void unSetMode() override; diff --git a/src/modules/graphics/opengl/Shader.cpp b/src/modules/graphics/opengl/Shader.cpp index 53ef5dc24..bdcc4d8ab 100644 --- a/src/modules/graphics/opengl/Shader.cpp +++ b/src/modules/graphics/opengl/Shader.cpp @@ -969,7 +969,7 @@ void Shader::updateBuiltinUniforms(love::graphics::Graphics *gfx, int viewportW, BuiltinUniformData data; data.transformMatrix = gfx->getTransform(); - data.projectionMatrix = gfx->getProjection(); + data.projectionMatrix = gfx->getDeviceProjection(); // The normal matrix is the transpose of the inverse of the rotation portion // (top-left 3x3) of the transform matrix. diff --git a/src/modules/graphics/wrap_Graphics.cpp b/src/modules/graphics/wrap_Graphics.cpp index 1d6fe6e90..38a4bb47b 100644 --- a/src/modules/graphics/wrap_Graphics.cpp +++ b/src/modules/graphics/wrap_Graphics.cpp @@ -1836,21 +1836,22 @@ int w_newIndexBuffer(lua_State *L) return 1; } -static PrimitiveType luax_optmeshdrawmode(lua_State *L, int idx, PrimitiveType def) +static PrimitiveType luax_checkmeshdrawmode(lua_State *L, int idx) { - const char *modestr = lua_isnoneornil(L, idx) ? nullptr : luaL_checkstring(L, idx); + const char *modestr = luaL_checkstring(L, idx); - if (modestr && !getConstant(modestr, def)) - luax_enumerror(L, "mesh draw mode", getConstants(def), modestr); + PrimitiveType mode = PRIMITIVE_TRIANGLES; + if (!getConstant(modestr, mode)) + luax_enumerror(L, "mesh draw mode", getConstants(mode), modestr); - return def; + return mode; } static Mesh *newStandardMesh(lua_State *L) { Mesh *t = nullptr; - PrimitiveType drawmode = luax_optmeshdrawmode(L, 2, PRIMITIVE_TRIANGLE_FAN); + PrimitiveType drawmode = luax_checkmeshdrawmode(L, 2); BufferDataUsage usage = luax_optdatausage(L, 3, BUFFERDATAUSAGE_DYNAMIC); std::vector format = Mesh::getDefaultVertexFormat(); @@ -1912,7 +1913,7 @@ static Mesh *newCustomMesh(lua_State *L) // the number of vertices. std::vector vertexformat; - PrimitiveType drawmode = luax_optmeshdrawmode(L, 3, PRIMITIVE_TRIANGLE_FAN); + PrimitiveType drawmode = luax_checkmeshdrawmode(L, 3); BufferDataUsage usage = luax_optdatausage(L, 4, BUFFERDATAUSAGE_DYNAMIC); lua_rawgeti(L, 1, 1); @@ -3572,6 +3573,34 @@ int w_inverseTransformPoint(lua_State *L) return 2; } +int w_setOrthoProjection(lua_State *L) +{ + float w = (float) luaL_checknumber(L, 1); + float h = (float) luaL_checknumber(L, 2); + float near = (float) luaL_optnumber(L, 3, -10.0); + float far = (float) luaL_optnumber(L, 4, 10.0); + + luax_catchexcept(L, [&]() { instance()->setOrthoProjection(w, h, near, far); }); + return 0; +} + +int w_setPerspectiveProjection(lua_State *L) +{ + float verticalfov = (float) luaL_checknumber(L, 1); + float aspect = (float) luaL_checknumber(L, 2); + float near = (float) luaL_checknumber(L, 3); + float far = (float) luaL_checknumber(L, 4); + + luax_catchexcept(L, [&]() { instance()->setPerspectiveProjection(verticalfov, aspect, near, far); }); + return 0; +} + +int w_resetProjection(lua_State */*L*/) +{ + instance()->resetProjection(); + return 0; +} + // List of functions to wrap. static const luaL_Reg functions[] = @@ -3710,6 +3739,10 @@ static const luaL_Reg functions[] = { "transformPoint", w_transformPoint }, { "inverseTransformPoint", w_inverseTransformPoint }, + { "setOrthoProjection", w_setOrthoProjection }, + { "setPerspectiveProjection", w_setPerspectiveProjection }, + { "resetProjection", w_resetProjection }, + // Deprecated { "newImage", w_newImage }, { "newArrayImage", w_newArrayImage },