From 5a2f30c5ae7cc7261998a7df72d5564b0b37944a Mon Sep 17 00:00:00 2001 From: binary1248 Date: Sun, 10 Jun 2018 20:57:15 +0200 Subject: [PATCH] Added support for scissor testing. --- include/SFML/Graphics/RenderTarget.hpp | 16 ++++++ include/SFML/Graphics/View.hpp | 63 ++++++++++++++++++++++ src/SFML/Graphics/RenderTarget.cpp | 47 ++++++++++++++-- src/SFML/Graphics/RenderTextureImplFBO.cpp | 12 +++++ src/SFML/Graphics/Texture.cpp | 12 +++++ src/SFML/Graphics/View.cpp | 21 ++++++++ test/Graphics/View.test.cpp | 14 +++++ 7 files changed, 181 insertions(+), 4 deletions(-) diff --git a/include/SFML/Graphics/RenderTarget.hpp b/include/SFML/Graphics/RenderTarget.hpp index d39ca9b5e..762e9b030 100644 --- a/include/SFML/Graphics/RenderTarget.hpp +++ b/include/SFML/Graphics/RenderTarget.hpp @@ -153,6 +153,21 @@ public: //////////////////////////////////////////////////////////// IntRect getViewport(const View& view) const; + //////////////////////////////////////////////////////////// + /// \brief Get the scissor rectangle of a view, applied to this render target + /// + /// The scissor rectangle is defined in the view as a ratio. This + /// function simply applies this ratio to the current dimensions + /// of the render target to calculate the pixels rectangle + /// that the scissor rectangle actually covers in the target. + /// + /// \param view The view for which we want to compute the scissor rectangle + /// + /// \return Scissor rectangle, expressed in pixels + /// + //////////////////////////////////////////////////////////// + IntRect getScissor(const View& view) const; + //////////////////////////////////////////////////////////// /// \brief Convert a point from target coordinates to world /// coordinates, using the current view @@ -496,6 +511,7 @@ private: bool enable; //!< Is the cache enabled? bool glStatesSet{}; //!< Are our internal GL states set yet? bool viewChanged; //!< Has the current view changed since last draw? + bool scissorEnabled; //!< Is scissor testing enabled? BlendMode lastBlendMode; //!< Cached blending mode std::uint64_t lastTextureId; //!< Cached texture bool texCoordsArrayEnabled; //!< Is GL_TEXTURE_COORD_ARRAY client state enabled? diff --git a/include/SFML/Graphics/View.hpp b/include/SFML/Graphics/View.hpp index 4b226f0ca..51ad64d10 100644 --- a/include/SFML/Graphics/View.hpp +++ b/include/SFML/Graphics/View.hpp @@ -119,6 +119,30 @@ public: //////////////////////////////////////////////////////////// void setViewport(const FloatRect& viewport); + //////////////////////////////////////////////////////////// + /// \brief Set the target scissor rectangle + /// + /// The scissor rectangle, expressed as a factor (between 0 and 1) of + /// the RenderTarget, specifies the region of the RenderTarget whose + /// pixels are able to be modified by draw or clear operations. + /// Any pixels which lie outside of the scissor rectangle will + /// not be modified by draw or clear operations. + /// For example, a scissor rectangle which only allows modifications + /// to the right side of the target would be defined + /// with View.setScissor(sf::FloatRect({0.5f, 0.f}, {0.5f, 1.f})). + /// By default, a view has a scissor rectangle which allows + /// modifications to the entire target. This is equivalent to + /// disabling the scissor test entirely. Passing the default + /// scissor rectangle to this function will also disable + /// scissor testing. + /// + /// \param scissor New scissor rectangle + /// + /// \see getScissor + /// + //////////////////////////////////////////////////////////// + void setScissor(const FloatRect& scissor); + //////////////////////////////////////////////////////////// /// \brief Reset the view to the given rectangle /// @@ -171,6 +195,16 @@ public: //////////////////////////////////////////////////////////// const FloatRect& getViewport() const; + //////////////////////////////////////////////////////////// + /// \brief Get the scissor rectangle of the view + /// + /// \return Scissor rectangle, expressed as a factor of the target size + /// + /// \see setScissor + /// + //////////////////////////////////////////////////////////// + const FloatRect& getScissor() const; + //////////////////////////////////////////////////////////// /// \brief Move the view relatively to its current position /// @@ -240,6 +274,7 @@ private: Vector2f m_size; //!< Size of the view, in scene coordinates Angle m_rotation; //!< Angle of rotation of the view rectangle FloatRect m_viewport{{0, 0}, {1, 1}}; //!< Viewport rectangle, expressed as a factor of the render-target's size + FloatRect m_scissor{{0, 0}, {1, 1}}; //!< Scissor rectangle, expressed as a factor of the render-target's size mutable Transform m_transform; //!< Precomputed projection transform corresponding to the view mutable Transform m_inverseTransform; //!< Precomputed inverse projection transform corresponding to the view mutable bool m_transformUpdated{}; //!< Internal state telling if the transform needs to be updated @@ -269,6 +304,34 @@ private: /// rectangle doesn't have the same size as the viewport, its /// contents will be stretched to fit in. /// +/// The scissor rectangle allows for specifying regions of the +/// render target to which modifications can be made by draw +/// and clear operations. Only pixels that are within the region +/// will be able to be modified. Pixels outside of the region will +/// not be modified by draw or clear operations. +/// +/// Certain effects can be created by either using the viewport or +/// scissor rectangle. While the results appear identical, there +/// can be times where one method should be preferred over the other. +/// Viewport transformations are applied during the vertex processing +/// stage of the graphics pipeline, before the primitives are +/// rasterized into fragments for fragment processing. Since +/// viewport processing has to be performed and cannot be disabled, +/// effects that are performed using the viewport transform are +/// basically free performance-wise. Scissor testing is performed in +/// the per-sample processing stage of the graphics pipeline, after +/// fragment processing has been performed. Because per-sample +/// processing is performed at the last stage of the pipeline, +/// fragments that are discarded at this stage will cause the +/// highest waste of GPU resources compared to any method that +/// would have discarded vertices or fragments earlier in the +/// pipeline. There are situations in which scissor testing has +/// to be used to control whether fragments are discarded or not. +/// An example of such a situation is when performing the viewport +/// transform on vertices is necessary but a subset of the generated +/// fragments should not have an effect on the stencil buffer or +/// blend with the color buffer. +// /// To apply a view, you have to assign it to the render target. /// Then, objects drawn in this render target will be /// affected by the view until you use another view. diff --git a/src/SFML/Graphics/RenderTarget.cpp b/src/SFML/Graphics/RenderTarget.cpp index a092f40e7..229a7d228 100644 --- a/src/SFML/Graphics/RenderTarget.cpp +++ b/src/SFML/Graphics/RenderTarget.cpp @@ -161,6 +161,10 @@ void RenderTarget::clear(const Color& color) // Unbind texture to fix RenderTexture preventing clear applyTexture(nullptr); + // Apply the view (scissor testing can affect clearing) + if (!m_cache.enable || m_cache.viewChanged) + applyCurrentView(); + glCheck(glClearColor(color.r / 255.f, color.g / 255.f, color.b / 255.f, color.a / 255.f)); glCheck(glClear(GL_COLOR_BUFFER_BIT)); } @@ -200,6 +204,17 @@ IntRect RenderTarget::getViewport(const View& view) const } +//////////////////////////////////////////////////////////// +IntRect RenderTarget::getScissor(const View& view) const +{ + const auto [width, height] = Vector2f(getSize()); + const FloatRect& scissor = view.getScissor(); + + return IntRect(Rect({std::lround(width * scissor.left), std::lround(height * scissor.top)}, + {std::lround(width * scissor.width), std::lround(height * scissor.height)})); +} + + //////////////////////////////////////////////////////////// Vector2f RenderTarget::mapPixelToCoords(const Vector2i& point) const { @@ -506,6 +521,7 @@ void RenderTarget::resetGLStates() glCheck(glDisable(GL_LIGHTING)); glCheck(glDisable(GL_DEPTH_TEST)); glCheck(glDisable(GL_ALPHA_TEST)); + glCheck(glDisable(GL_SCISSOR_TEST)); glCheck(glEnable(GL_TEXTURE_2D)); glCheck(glEnable(GL_BLEND)); glCheck(glMatrixMode(GL_MODELVIEW)); @@ -513,7 +529,8 @@ void RenderTarget::resetGLStates() glCheck(glEnableClientState(GL_VERTEX_ARRAY)); glCheck(glEnableClientState(GL_COLOR_ARRAY)); glCheck(glEnableClientState(GL_TEXTURE_COORD_ARRAY)); - m_cache.glStatesSet = true; + m_cache.scissorEnabled = false; + m_cache.glStatesSet = true; // Apply the default SFML states applyBlendMode(BlendAlpha); @@ -556,9 +573,31 @@ void RenderTarget::initialize() void RenderTarget::applyCurrentView() { // Set the viewport - const IntRect viewport = getViewport(m_view); - const int top = static_cast(getSize().y) - (viewport.top + viewport.height); - glCheck(glViewport(viewport.left, top, viewport.width, viewport.height)); + const IntRect viewport = getViewport(m_view); + const int viewportTop = static_cast(getSize().y) - (viewport.top + viewport.height); + glCheck(glViewport(viewport.left, viewportTop, viewport.width, viewport.height)); + + // Set the scissor rectangle and enable/disable scissor testing + if (m_view.getScissor() == FloatRect({0, 0}, {1, 1})) + { + if (m_cache.scissorEnabled) + { + glCheck(glDisable(GL_SCISSOR_TEST)); + m_cache.scissorEnabled = false; + } + } + else + { + const IntRect pixelScissor = getScissor(m_view); + const int scissorTop = static_cast(getSize().y) - (pixelScissor.top + pixelScissor.height); + glCheck(glScissor(pixelScissor.left, scissorTop, pixelScissor.width, pixelScissor.height)); + + if (!m_cache.scissorEnabled) + { + glCheck(glEnable(GL_SCISSOR_TEST)); + m_cache.scissorEnabled = true; + } + } // Set the projection matrix glCheck(glMatrixMode(GL_PROJECTION)); diff --git a/src/SFML/Graphics/RenderTextureImplFBO.cpp b/src/SFML/Graphics/RenderTextureImplFBO.cpp index d9fbf8685..804a1b3d2 100644 --- a/src/SFML/Graphics/RenderTextureImplFBO.cpp +++ b/src/SFML/Graphics/RenderTextureImplFBO.cpp @@ -572,6 +572,14 @@ void RenderTextureImplFBO::updateTexture(unsigned int) if (frameBuffer && multiSampleFrameBuffer) { + // Scissor testing affects framebuffer blits as well + // Since we don't want scissor testing to interfere with our copying, we temporarily disable it for the blit if it is enabled + GLboolean scissorEnabled = GL_FALSE; + glCheck(glGetBooleanv(GL_SCISSOR_TEST, &scissorEnabled)); + + if (scissorEnabled == GL_TRUE) + glCheck(glDisable(GL_SCISSOR_TEST)); + // Set up the blit target (draw framebuffer) and blit (from the read framebuffer, our multisample FBO) glCheck(GLEXT_glBindFramebuffer(GLEXT_GL_DRAW_FRAMEBUFFER, frameBuffer->object)); glCheck(GLEXT_glBlitFramebuffer(0, @@ -585,6 +593,10 @@ void RenderTextureImplFBO::updateTexture(unsigned int) GL_COLOR_BUFFER_BIT, GL_NEAREST)); glCheck(GLEXT_glBindFramebuffer(GLEXT_GL_DRAW_FRAMEBUFFER, multiSampleFrameBuffer->object)); + + // Re-enable scissor testing if it was previously enabled + if (scissorEnabled == GL_TRUE) + glCheck(glEnable(GL_SCISSOR_TEST)); } } } diff --git a/src/SFML/Graphics/Texture.cpp b/src/SFML/Graphics/Texture.cpp index 3e66898c0..a4b373c01 100644 --- a/src/SFML/Graphics/Texture.cpp +++ b/src/SFML/Graphics/Texture.cpp @@ -582,6 +582,14 @@ void Texture::update(const Texture& texture, const Vector2u& dest) if ((sourceStatus == GLEXT_GL_FRAMEBUFFER_COMPLETE) && (destStatus == GLEXT_GL_FRAMEBUFFER_COMPLETE)) { + // Scissor testing affects framebuffer blits as well + // Since we don't want scissor testing to interfere with our copying, we temporarily disable it for the blit if it is enabled + GLboolean scissorEnabled = GL_FALSE; + glCheck(glGetBooleanv(GL_SCISSOR_TEST, &scissorEnabled)); + + if (scissorEnabled == GL_TRUE) + glCheck(glDisable(GL_SCISSOR_TEST)); + // Blit the texture contents from the source to the destination texture glCheck(GLEXT_glBlitFramebuffer(0, texture.m_pixelsFlipped ? static_cast(texture.m_size.y) : 0, @@ -593,6 +601,10 @@ void Texture::update(const Texture& texture, const Vector2u& dest) static_cast(dest.y + texture.m_size.y), // Destination rectangle GL_COLOR_BUFFER_BIT, GL_NEAREST)); + + // Re-enable scissor testing if it was previously enabled + if (scissorEnabled == GL_TRUE) + glCheck(glEnable(GL_SCISSOR_TEST)); } else { diff --git a/src/SFML/Graphics/View.cpp b/src/SFML/Graphics/View.cpp index 51726631a..b566a6f0f 100644 --- a/src/SFML/Graphics/View.cpp +++ b/src/SFML/Graphics/View.cpp @@ -87,6 +87,20 @@ void View::setViewport(const FloatRect& viewport) } +//////////////////////////////////////////////////////////// +void View::setScissor(const FloatRect& scissor) +{ + assert(scissor.left >= 0.0f && scissor.left <= 1.0f && "scissor.left must lie within [0, 1]"); + assert(scissor.top >= 0.0f && scissor.top <= 1.0f && "scissor.top must lie within [0, 1]"); + assert(scissor.width >= 0.0f && "scissor.width must lie within [0, 1]"); + assert(scissor.height >= 0.0f && "scissor.height must lie within [0, 1]"); + assert(scissor.left + scissor.width <= 1.0f && "scissor.left + scissor.width must lie within [0, 1]"); + assert(scissor.top + scissor.height <= 1.0f && "scissor.top + scissor.height must lie within [0, 1]"); + + m_scissor = scissor; +} + + //////////////////////////////////////////////////////////// void View::reset(const FloatRect& rectangle) { @@ -127,6 +141,13 @@ const FloatRect& View::getViewport() const } +//////////////////////////////////////////////////////////// +const FloatRect& View::getScissor() const +{ + return m_scissor; +} + + //////////////////////////////////////////////////////////// void View::move(const Vector2f& offset) { diff --git a/test/Graphics/View.test.cpp b/test/Graphics/View.test.cpp index 02daa903a..f9b3497d9 100644 --- a/test/Graphics/View.test.cpp +++ b/test/Graphics/View.test.cpp @@ -24,6 +24,7 @@ TEST_CASE("[Graphics] sf::View") CHECK(view.getSize() == sf::Vector2f(1000, 1000)); CHECK(view.getRotation() == sf::Angle::Zero); CHECK(view.getViewport() == sf::FloatRect({0, 0}, {1, 1})); + CHECK(view.getScissor() == sf::FloatRect({0, 0}, {1, 1})); CHECK(view.getTransform() == sf::Transform(0.002f, 0, -1, 0, -0.002f, 1, 0, 0, 1)); CHECK(view.getInverseTransform() == Approx(sf::Transform(500, 0, 500, 0, -500, 500, 0, 0, 1))); } @@ -35,6 +36,7 @@ TEST_CASE("[Graphics] sf::View") CHECK(view.getSize() == sf::Vector2f(400, 600)); CHECK(view.getRotation() == sf::Angle::Zero); CHECK(view.getViewport() == sf::FloatRect({0, 0}, {1, 1})); + CHECK(view.getScissor() == sf::FloatRect({0, 0}, {1, 1})); CHECK(view.getTransform() == Approx(sf::Transform(0.005f, 0, -1.05f, 0, -0.00333333f, 1.06667f, 0, 0, 1))); CHECK(view.getInverseTransform() == Approx(sf::Transform(200, 0, 210, 0, -300, 320, 0, 0, 1))); } @@ -46,6 +48,7 @@ TEST_CASE("[Graphics] sf::View") CHECK(view.getSize() == sf::Vector2f(1080, 1920)); CHECK(view.getRotation() == sf::Angle::Zero); CHECK(view.getViewport() == sf::FloatRect({0, 0}, {1, 1})); + CHECK(view.getScissor() == sf::FloatRect({0, 0}, {1, 1})); CHECK(view.getTransform() == Approx(sf::Transform(0.00185185f, 0, -0.962963f, 0, -0.00104167f, 1, 0, 0, 1))); CHECK(view.getInverseTransform() == Approx(sf::Transform(540, 0, 520, 0, -960, 960, 0, 0, 1))); } @@ -93,6 +96,15 @@ TEST_CASE("[Graphics] sf::View") CHECK(view.getViewport() == sf::FloatRect({150, 250}, {500, 750})); CHECK(view.getTransform() == Approx(sf::Transform(0.002f, 0, -1, 0, -0.002f, 1, 0, 0, 1))); CHECK(view.getInverseTransform() == Approx(sf::Transform(500, 0, 500, 0, -500, 500, 0, 0, 1))); + CHECK(view.getScissor() == sf::FloatRect({0, 0}, {1, 1})); + } + + SECTION("Set/get scissor") + { + sf::View view; + view.setScissor({{0, 0}, {0.5f, 1}}); + CHECK(view.getScissor() == sf::FloatRect({0, 0}, {0.5, 1})); + CHECK(view.getViewport() == sf::FloatRect({0, 0}, {1, 1})); } SECTION("reset()") @@ -102,11 +114,13 @@ TEST_CASE("[Graphics] sf::View") view.setSize({600, 900}); view.setRotation(sf::degrees(15)); view.setViewport({{150, 250}, {500, 750}}); + view.setScissor({{0.2f, 0.3f}, {0.4f, 0.5f}}); view.reset({{1, 2}, {3, 4}}); CHECK(view.getCenter() == sf::Vector2f(2.5f, 4)); CHECK(view.getSize() == sf::Vector2f(3, 4)); CHECK(view.getRotation() == sf::Angle::Zero); CHECK(view.getViewport() == sf::FloatRect({150, 250}, {500, 750})); + CHECK(view.getScissor() == sf::FloatRect({0.2f, 0.3f}, {0.4f, 0.5f})); CHECK(view.getTransform() == Approx(sf::Transform(0.666667f, 0, -1.66667f, 0, -0.5f, 2, 0, 0, 1))); CHECK(view.getInverseTransform() == Approx(sf::Transform(1.5f, 0, 2.5f, 0, -2, 4, 0, 0, 1))); }