Add basic ortho and perspective projection APIs to love.graphics.

- Added love.graphics.setOrthoProjection.
- Added love.graphics.setPerspectiveProjection.
- Added love.graphics.resetProjection.

Note that the projection is reset whenever the canvas changes.
This commit is contained in:
Alex Szpakowski
2022-01-08 22:56:21 -04:00
parent 7f3538d2ba
commit ff83ee6a9c
8 changed files with 219 additions and 15 deletions
+92 -3
View File
@@ -118,7 +118,7 @@ Graphics::Graphics()
, active(true)
, writingToStencil(false)
, batchedDrawState()
, projectionMatrix()
, deviceProjectionMatrix()
, renderTargetSwitchCount(0)
, drawCalls(0)
, drawCallsBatched(0)
@@ -472,6 +472,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)
@@ -548,6 +553,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
@@ -801,6 +811,8 @@ void Graphics::setRenderTargets(const RenderTargets &rts)
std::swap(state.renderTargets, refs);
renderTargetSwitchCount++;
resetProjection();
}
void Graphics::setRenderTarget()
@@ -815,6 +827,8 @@ void Graphics::setRenderTarget()
state.renderTargets = RenderTargetsStrongRef();
renderTargetSwitchCount++;
resetProjection();
}
Graphics::RenderTargets Graphics::getRenderTargets() const
@@ -2020,9 +2034,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()
@@ -2102,6 +2116,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 },
+23 -2
View File
@@ -824,7 +824,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);
@@ -838,6 +838,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;
@@ -870,6 +877,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();
@@ -906,6 +921,9 @@ protected:
bool wireframe = false;
bool useCustomProjection = false;
Matrix4 customProjection;
// Default mipmap filter is set in the DisplayState constructor.
SamplerState defaultSamplerState = SamplerState();
};
@@ -967,6 +985,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;
@@ -984,7 +1005,7 @@ protected:
BatchedDrawState batchedDrawState;
std::vector<Matrix4> transformStack;
Matrix4 projectionMatrix;
Matrix4 deviceProjectionMatrix;
std::vector<double> pixelScaleStack;
+15 -9
View File
@@ -165,6 +165,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;
@@ -182,8 +194,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);
@@ -719,7 +730,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();
@@ -734,19 +745,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;
}
+2
View File
@@ -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<Buffer::DataDeclaration> &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(int width, int height, int pixelwidth, int pixelheight, bool windowhasstencil, int msaa) override;
void unSetMode() override;
+1 -1
View File
@@ -1081,7 +1081,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.
+32
View File
@@ -3572,6 +3572,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 +3738,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 },