diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index db15f04a7..43c6c5b95 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -20,6 +20,7 @@ endif() if(SFML_BUILD_GRAPHICS) add_subdirectory(opengl) + add_subdirectory(stencil) if(NOT SFML_OS_IOS) add_subdirectory(joystick) diff --git a/examples/stencil/CMakeLists.txt b/examples/stencil/CMakeLists.txt new file mode 100644 index 000000000..b6dee91e1 --- /dev/null +++ b/examples/stencil/CMakeLists.txt @@ -0,0 +1,7 @@ +# all source files +set(SRC Stencil.cpp) + +# define the stencil target +sfml_add_example(stencil GUI_APP + SOURCES ${SRC} + DEPENDS SFML::Graphics) diff --git a/examples/stencil/Stencil.cpp b/examples/stencil/Stencil.cpp new file mode 100644 index 000000000..d9e8cdfc7 --- /dev/null +++ b/examples/stencil/Stencil.cpp @@ -0,0 +1,138 @@ +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include + +//////////////////////////////////////////////////////////// +/// Entry point of application +/// +/// \return Application exit code +/// +//////////////////////////////////////////////////////////// +int main() +{ + // Create the window of the application with a stencil buffer + sf::RenderWindow window(sf::VideoMode({600, 600}), + "SFML Stencil", + sf::Style::Titlebar | sf::Style::Close, + sf::State::Windowed, + sf::ContextSettings(0, 8)); + window.setVerticalSyncEnabled(true); + + sf::RectangleShape red({500, 50}); + red.setFillColor(sf::Color::Red); + red.setPosition({270, 70}); + red.setRotation(sf::degrees(60)); + + sf::RectangleShape green({500, 50}); + green.setFillColor(sf::Color::Green); + green.setPosition({370, 100}); + green.setRotation(sf::degrees(120)); + + sf::RectangleShape blue({500, 50}); + blue.setFillColor(sf::Color::Blue); + blue.setPosition({550, 470}); + blue.setRotation(sf::degrees(180)); + + while (window.isOpen()) + { + // Handle events + for (sf::Event event; window.pollEvent(event);) + { + // Window closed: exit + if (event.type == sf::Event::Closed) + { + window.close(); + break; + } + } + + // When drawing using a 2D API, we normally resort to what is known as the "painter's algorithm". + // Because our graphics primitives lack any depth information, objects that are drawn later in the frame will + // overlap objects drawn earlier in the frame. This means that the objects have to be sorted from farthest to + // closest and drawn in that order to appear correct. + // This also means that objects cannot simultaneously be both "in front" and "behind" already drawn objects. + + // With the magic of the stencil buffer we can get around this rule. Much like the depth buffer in 3D applications + // the stencil buffer holds additional information that informs the rendering pipeline about our intentions. + // Unlike the depth buffer which requires that all drawn primitives contain depth information (e.g. in the form + // of 3D vertices), the stencil buffer allows us to specify stencil values for whole primitives ourselves. + + // For every fragment/pixel that would be written to the screen, after the fragment shader is executed, the stencil + // test is performed. First, the rendering pipeline will ask whether the pixel in question should even be kept or + // discarded. This is known as the stencil test. In order to answer this question 2 integer values are compared + // against each other, the value already in the screen stencil buffer corresponding to the pixel in question and + // the new value of the incoming pixel to perform the test for. The value of the incoming pixel is known as the + // reference value and can be set per draw operation when using the sfml-graphics drawing API. All mathematical + // operations comparing 2 integers are supported: Less, LessEqual, Greater, GreaterEqual, Equal, NotEqual. + // Additionally, 2 special comparisons are provided: Always and Never. Always will make sure the stencil test + // will always pass, whereas Never will make sure the stencil test never passes. The incoming reference value + // is compared to the stencil buffer value in the following order: (ReferenceValue Comparison BufferValue) + // If the test evaluates to true, the pixel is kept, otherwise it is culled and will no longer contribute to + // the frame in any way. + + // Once the stencil test passes, the value in the stencil buffer can be updated with a new value. The new value + // is determined by the update operation and for the Replace operation the incoming reference value as well. + // In the case of Increment, Decrement and Invert, the existing value in the stencil buffer is modified accordingly, + // Invert will perform a bit-wise inversion of the integer value in the buffer. Keep will not modify the value + // in the buffer whereas Zero will set it to 0. Replace will replace the value in the buffer with the incoming + // reference value. + + // Like all data types, the stencil values in the stencil buffer have a finite bit width. Typically stencil buffers + // with 8-bits are offered by the graphics implementation. In complex scenarios, we might want to partition our + // bits up into multiple areas so a single stencil buffer value can be used for multiple purposes simultaneously. + // For this purpose, we can specify a mask value that is bit-wise ANDed with both the incoming reference value + // and the stencil buffer value before they are compared. For simple cases, a mask of ~0 (all 1s) can be used which + // is the equivalent of disabling masking all together. + + // For certain effects, objects might have to be rendered multiple times. Once to establish the stencil value of + // that object within the stencil buffer and another to draw the object itself including its texture/color. Drawing + // objects with the sole purpose of updating stencil buffer values is also known as performing a stencil-only pass. + // Skipping texturing and writes to the color buffer can save a lot of time depending on the object to be drawn. + // StencilMode allows us to perform stencil-only drawing by setting the corresponding flag to true. + + // In this example, we demonstrate how can can draw 3 cyclically overlapping rectangles using the stencil buffer. + // Without the stencil buffer, 1 of the rectangles would have to be precisely split along the edge of another + // rectangle and both pieces would have to be drawn at different stages in the draw pass. This would not only be + // almost impossible to compute to the required accuracy to mimic the GPU's vertex computations but splitting a + // primitive up and drawing the pieces in seperate draw calls would introduce noticeable artifacts which would + // reduce the overall quality of the output image. + + // To start with, we initialize the stencil buffer values for every pixel to 0 at the start of each frame. In + // our draw calls we need to make sure that the stencil reference values of all objects will pass the test compared + // to the initial buffer value. In the case of Always, the initial value is insignificant, when we use Greater we + // make sure the reference value of 2 is greater than 0. + + // Clear the window color to black and the initial stencil buffer values to 0 + window.clear(sf::Color::Black, 0); + + // Draw rectangles + + // We draw the first rectangle with comparison set to always so that it will definitely draw and update (Replace) + // the stencil buffer values of its pixels to the specified reference value. + window.draw(red, + sf::StencilMode{sf::StencilComparison::Always, sf::StencilUpdateOperation::Replace, 3, ~0u, false}); + + // Just like the first, we draw the second rectangle with comparison set to always so that it will definitely + // draw and update (Replace) the stencil buffer values of its pixels to the specified reference value. + // In the case of pixels overlapping the first rectangle, because we specify Always as the comparison, it is + // as if we are drawing using the painter's algorithm, i.e. newer pixels overwrite older pixels. + window.draw(green, + sf::StencilMode{sf::StencilComparison::Always, sf::StencilUpdateOperation::Replace, 1, ~0u, false}); + + // Now comes the magic. We want to draw the third rectangle so it is behind i.e. does not overwrite pixels of the + // first rectangle but in front of i.e. overwrites pixels of the second rectangle. We already set the reference + // value of the first rectangle to 3 and the second rectangle to 1, so in order to be "between" them, this rectangle + // has to have a reference value of 2. 2 is not greather than 3 so pixels of this rectangle will not overwrite pixels + // of the first rectangle, however 2 is greater than 1 and thus pixels of this rectangle will overwrite pixels of the + // second rectangle. The stencil update operation for this draw operation is not significant in any way since this is + // the last draw call in the frame. + window.draw(blue, + sf::StencilMode{sf::StencilComparison::Greater, sf::StencilUpdateOperation::Replace, 2, ~0u, false}); + + // Display things on screen + window.display(); + } + + return EXIT_SUCCESS; +} diff --git a/examples/tennis/CMakeLists.txt b/examples/tennis/CMakeLists.txt index 8095fd9c1..090ec78d4 100644 --- a/examples/tennis/CMakeLists.txt +++ b/examples/tennis/CMakeLists.txt @@ -7,7 +7,7 @@ if(SFML_OS_IOS) set_source_files_properties(${RESOURCES} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) endif() -# define the pong target +# define the tennis target sfml_add_example(tennis GUI_APP SOURCES ${SRC} BUNDLE_RESOURCES ${RESOURCES} diff --git a/include/SFML/Graphics.hpp b/include/SFML/Graphics.hpp index 8731a760e..1440728dc 100644 --- a/include/SFML/Graphics.hpp +++ b/include/SFML/Graphics.hpp @@ -46,6 +46,7 @@ #include #include #include +#include #include #include #include diff --git a/include/SFML/Graphics/RenderStates.hpp b/include/SFML/Graphics/RenderStates.hpp index 179edd983..a67c57f81 100644 --- a/include/SFML/Graphics/RenderStates.hpp +++ b/include/SFML/Graphics/RenderStates.hpp @@ -31,6 +31,7 @@ #include #include +#include #include @@ -52,6 +53,7 @@ struct SFML_GRAPHICS_API RenderStates /// to using sf::RenderStates::Default. /// The default set defines: /// \li the BlendAlpha blend mode + /// \li the default StencilMode (no stencil) /// \li the identity transform /// \li a null texture /// \li a null shader @@ -67,6 +69,14 @@ struct SFML_GRAPHICS_API RenderStates //////////////////////////////////////////////////////////// RenderStates(const BlendMode& theBlendMode); + //////////////////////////////////////////////////////////// + /// \brief Construct a default set of render states with a custom stencil mode + /// + /// \param theStencilMode Stencil mode to use + /// + //////////////////////////////////////////////////////////// + RenderStates(const StencilMode& theStencilMode); + //////////////////////////////////////////////////////////// /// \brief Construct a default set of render states with a custom transform /// @@ -95,17 +105,19 @@ struct SFML_GRAPHICS_API RenderStates /// \brief Construct a set of render states with all its attributes /// /// \param theBlendMode Blend mode to use + /// \param theStencilMode Stencil mode to use /// \param theTransform Transform to use /// \param theCoordinateType Texture coordinate type to use /// \param theTexture Texture to use /// \param theShader Shader to use /// //////////////////////////////////////////////////////////// - RenderStates(const BlendMode& theBlendMode, - const Transform& theTransform, - CoordinateType theCoordinateType, - const Texture* theTexture, - const Shader* theShader); + RenderStates(const BlendMode& theBlendMode, + const StencilMode& theStencilMode, + const Transform& theTransform, + CoordinateType theCoordinateType, + const Texture* theTexture, + const Shader* theShader); //////////////////////////////////////////////////////////// // Static member data @@ -117,6 +129,7 @@ struct SFML_GRAPHICS_API RenderStates // Member data //////////////////////////////////////////////////////////// BlendMode blendMode{BlendAlpha}; //!< Blending mode + StencilMode stencilMode; //!< Stencil mode Transform transform; //!< Transform CoordinateType coordinateType{CoordinateType::Pixels}; //!< Texture coordinate type const Texture* texture{}; //!< Texture @@ -130,9 +143,10 @@ struct SFML_GRAPHICS_API RenderStates /// \class sf::RenderStates /// \ingroup graphics /// -/// There are five global states that can be applied to +/// There are six global states that can be applied to /// the drawn objects: /// \li the blend mode: how pixels of the object are blended with the background +/// \li the stencil mode: how pixels of the object interact with the stencil buffer /// \li the transform: how the object is positioned/rotated/scaled /// \li the texture coordinate type: how texture coordinates are interpreted /// \li the texture: what image is mapped to the object diff --git a/include/SFML/Graphics/RenderTarget.hpp b/include/SFML/Graphics/RenderTarget.hpp index 0cc4b39f4..e2c3382a1 100644 --- a/include/SFML/Graphics/RenderTarget.hpp +++ b/include/SFML/Graphics/RenderTarget.hpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -97,6 +98,29 @@ public: //////////////////////////////////////////////////////////// void clear(const Color& color = Color::Black); + //////////////////////////////////////////////////////////// + /// \brief Clear the stencil buffer to a specific value + /// + /// The specified value is truncated to the bit width of + /// the current stencil buffer. + /// + /// \param value Stencil value to clear to + /// + //////////////////////////////////////////////////////////// + void clearStencil(StencilValue stencilValue); + + //////////////////////////////////////////////////////////// + /// \brief Clear the entire target with a single color and stencil value + /// + /// The specified stencil value is truncated to the bit + /// width of the current stencil buffer. + /// + /// \param color Fill color to use to clear the render target + /// \param value Stencil value to clear to + /// + //////////////////////////////////////////////////////////// + void clear(const Color& color, StencilValue stencilValue); + //////////////////////////////////////////////////////////// /// \brief Change the current active view /// @@ -452,6 +476,14 @@ private: //////////////////////////////////////////////////////////// void applyBlendMode(const BlendMode& mode); + //////////////////////////////////////////////////////////// + /// \brief Apply a new stencil mode + /// + /// \param mode Stencil mode to apply + /// + //////////////////////////////////////////////////////////// + void applyStencilMode(const StencilMode& mode); + //////////////////////////////////////////////////////////// /// \brief Apply a new transform /// @@ -514,7 +546,9 @@ private: 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? + bool stencilEnabled; //!< Is stencil testing enabled? BlendMode lastBlendMode; //!< Cached blending mode + StencilMode lastStencilMode; //!< Cached stencil std::uint64_t lastTextureId; //!< Cached texture CoordinateType lastCoordinateType; //!< Texture coordinate type bool texCoordsArrayEnabled; //!< Is GL_TEXTURE_COORD_ARRAY client state enabled? diff --git a/include/SFML/Graphics/StencilMode.hpp b/include/SFML/Graphics/StencilMode.hpp new file mode 100644 index 000000000..1c31b264b --- /dev/null +++ b/include/SFML/Graphics/StencilMode.hpp @@ -0,0 +1,256 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2023 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +#pragma once + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include + + +namespace sf +{ + +//////////////////////////////////////////////////////// +/// \brief Enumeration of the stencil test comparisons that can be performed +/// +/// The comparisons are mapped directly to their OpenGL equivalents, +/// specified by glStencilFunc(). +//////////////////////////////////////////////////////// +enum class StencilComparison +{ + Never, //!< The stencil test never passes + Less, //!< The stencil test passes if the new value is less than the value in the stencil buffer + LessEqual, //!< The stencil test passes if the new value is less than or equal to the value in the stencil buffer + Greater, //!< The stencil test passes if the new value is greater than the value in the stencil buffer + GreaterEqual, //!< The stencil test passes if the new value is greater than or equal to the value in the stencil buffer + Equal, //!< The stencil test passes if the new value is strictly equal to the value in the stencil buffer + NotEqual, //!< The stencil test passes if the new value is strictly inequal to the value in the stencil buffer + Always //!< The stencil test always passes +}; + +//////////////////////////////////////////////////////// +/// \brief Enumeration of the stencil buffer update operations +/// +/// The update operations are mapped directly to their OpenGL equivalents, +/// specified by glStencilOp(). +//////////////////////////////////////////////////////// +enum class StencilUpdateOperation +{ + Keep, //!< If the stencil test passes, the value in the stencil buffer is not modified + Zero, //!< If the stencil test passes, the value in the stencil buffer is set to zero + Replace, //!< If the stencil test passes, the value in the stencil buffer is set to the new value + Increment, //!< If the stencil test passes, the value in the stencil buffer is incremented and if required clamped + Decrement, //!< If the stencil test passes, the value in the stencil buffer is decremented and if required clamped + Invert, //!< If the stencil test passes, the value in the stencil buffer is bitwise inverted +}; + +//////////////////////////////////////////////////////// +/// \brief Stencil value type (also used as a mask) +/// +//////////////////////////////////////////////////////// +struct SFML_GRAPHICS_API StencilValue +{ + //////////////////////////////////////////////////////////// + /// \brief Construct a stencil value from a signed integer + /// + /// \param theValue Signed integer value to use + /// + //////////////////////////////////////////////////////////// + StencilValue(int theValue); + + //////////////////////////////////////////////////////////// + /// \brief Construct a stencil value from an unsigned integer + /// + /// \param theValue Unsigned integer value to use + /// + //////////////////////////////////////////////////////////// + StencilValue(unsigned int theValue); + + //////////////////////////////////////////////////////////// + /// \brief Disable construction from any other type + /// + //////////////////////////////////////////////////////////// + template + StencilValue(T) = delete; + + unsigned int value = 0u; //!< The stored stencil value +}; + +//////////////////////////////////////////////////////////// +/// \brief Stencil modes for drawing +/// +//////////////////////////////////////////////////////////// +struct SFML_GRAPHICS_API StencilMode +{ + StencilComparison stencilComparison{StencilComparison::Always}; //!< The comparison we're performing the stencil test with + StencilUpdateOperation stencilUpdateOperation{ + StencilUpdateOperation::Keep}; //!< The update operation to perform if the stencil test passes + StencilValue stencilReference{0}; //!< The reference value we're performing the stencil test with + StencilValue stencilMask{~0u}; //!< The mask to apply to both the reference value and the value in the stencil buffer + bool stencilOnly{}; //!< Whether we should update the color buffer in addition to the stencil buffer +}; + +//////////////////////////////////////////////////////////// +/// \relates StencilMode +/// \brief Overload of the == operator +/// +/// \param left Left operand +/// \param right Right operand +/// +/// \return True if stencil modes are equal, false if they are different +/// +//////////////////////////////////////////////////////////// +SFML_GRAPHICS_API bool operator==(const StencilMode& left, const StencilMode& right); + +//////////////////////////////////////////////////////////// +/// \relates StencilMode +/// \brief Overload of the != operator +/// +/// \param left Left operand +/// \param right Right operand +/// +/// \return True if stencil modes are different, false if they are equal +/// +//////////////////////////////////////////////////////////// +SFML_GRAPHICS_API bool operator!=(const StencilMode& left, const StencilMode& right); + +} // namespace sf + + +//////////////////////////////////////////////////////////// +/// \class sf::StencilMode +/// \ingroup graphics +/// +/// sf::StencilMode is a class that controls stencil testing. +/// +/// In addition to drawing to the visible portion of a render target, +/// there is the possibility to "draw" to a so-called stencil buffer. +/// The stencil buffer is a special non-visible buffer that can contain +/// a single value per pixel that is drawn. This can be thought of as a +/// fifth value in addition to red, green, blue and alpha values. The maximum +/// value that can be represented depends on what is supported by the system. +/// Typically support for a 8-bit stencil buffer should always be available. +/// This will also have to be requested when creating a render target via +/// the sf::ContextSettings that is passed during creation. Stencil testing +/// will not work if there is no stencil buffer available in the target +/// that is being drawn to. +/// +/// Initially, just like with the visible color buffer, the stencil value of +/// each pixel is set to an undefined value. Calling sf::RenderTarget::clear +/// will set each pixel's stencil value to 0. sf::RenderTarget::clear can be +/// called at any time to reset the stencil values back to 0. +/// +/// When drawing an object, before each pixel of the color buffer is updated +/// with its new color value, the stencil test is performed. During this test +/// 2 values are compared with each other: the reference value that is passed +/// via sf::StencilMode and the value that is currently in the stencil buffer. +/// The arithmetic comparison that is performed on the 2 values can also be +/// controlled via sf::StencilMode. Depending on whether the test passes i.e. +/// the comparison yields true, the color buffer is updated with its new RGBA +/// value and if set in sf::StencilMode the stencil buffer is updated +/// accordingly. The new stencil value will be used during stencil testing the +/// next time the pixel is drawn to. +/// +/// The class is composed of 5 components, each of which has its +/// own public member variable: +/// \li Stencil Comparison (\ref stencilComparison) +/// \li Stencil Update Operation (\ref stencilUpdateOperation) +/// \li Stencil Reference Value (\ref stencilReference) +/// \li Stencil Mask Value (\ref stencilMask) +/// \li Stencil Only Update (\ref stencilOnly) +/// +/// The stencil comparison specifies the comparison that is performed between +/// the reference value of the currently active sf::StencilMode and the value +/// that is currently in the stencil buffer. This comparison determines whether +/// the stencil test passes or fails. +/// +/// The stencil update operation specifies how the stencil buffer is updated if +/// the stencil test passes. If the stencil test fails, neither the color or +/// stencil buffers will be modified. If incrementing or decrementing the +/// stencil value, the new value will be clamped to the range from 0 to the +/// maximum representable value given the bit width of the stencil buffer +/// e.g. 255 if an 8-bit stencil buffer is being used. +/// +/// The reference value is used both during the comparison with the current +/// stencil buffer value and as the new value to be written when the operation +/// is set to Replace. +/// +/// The mask value is used to mask the bits of both the reference value and +/// the value in the stencil buffer during the comparison and when updating. +/// The mask can be used to e.g. segment the stencil value bits into separate +/// regions that are used for different purposes. +/// +/// In certain situations, it might make sense to only write to the stencil +/// buffer and not the color buffer during a draw. The written stencil buffer +/// value can then be used in subsequent draws as a masking region. +/// +/// In SFML, a stencil mode can be specified every time you draw a sf::Drawable +/// object to a render target. It is part of the sf::RenderStates compound +/// that is passed to the member function sf::RenderTarget::draw(). +/// +/// Usage example: +/// \code +/// // Make sure we create a RenderTarget with a stencil buffer by specifying it via the context settings +/// sf::RenderWindow window(sf::VideoMode({250, 200}), "Stencil Window", sf::Style::Default, sf::ContextSettings(0, 8)); +/// +/// ... +/// +/// // Left circle +/// sf::CircleShape left(100.f); +/// left.setFillColor(sf::Color::Green); +/// left.setPosition({0, 0}); +/// +/// // Middle circle +/// sf::CircleShape middle(100.f); +/// middle.setFillColor(sf::Color::Yellow); +/// middle.setPosition({25, 0}); +/// +/// // Right circle +/// sf::CircleShape right(100.f); +/// right.setFillColor(sf::Color::Red); +/// right.setPosition({50, 0}); +/// +/// ... +/// +/// // Clear the stencil buffer to 0 at the start of every frame +/// window.clear(sf::Color::Black, 0); +/// +/// ... +/// +/// // Draw the middle circle in a stencil-only pass and write the value 1 +/// // to the stencil buffer for every pixel the circle would have affected +/// window.draw(middle, sf::StencilMode{sf::StencilComparison::Always, sf::StencilUpdateOperation::Replace, 1, 0xFF, true}); +/// +/// // Draw the left and right circles +/// // Only allow rendering to pixels whose stencil value is not +/// // equal to 1 i.e. weren't written when drawing the middle circle +/// window.draw(left, sf::StencilMode{sf::StencilComparison::NotEqual, sf::StencilUpdateOperation::Keep, 1, 0xFF, false}); +/// window.draw(right, sf::StencilMode{sf::StencilComparison::NotEqual, sf::StencilUpdateOperation::Keep, 1, 0xFF, false}); +/// \endcode +/// +/// \see sf::RenderStates, sf::RenderTarget +/// +//////////////////////////////////////////////////////////// diff --git a/src/SFML/Graphics/CMakeLists.txt b/src/SFML/Graphics/CMakeLists.txt index 229872f84..e436b05b1 100644 --- a/src/SFML/Graphics/CMakeLists.txt +++ b/src/SFML/Graphics/CMakeLists.txt @@ -34,6 +34,8 @@ set(SRC ${INCROOT}/RenderWindow.hpp ${SRCROOT}/Shader.cpp ${INCROOT}/Shader.hpp + ${SRCROOT}/StencilMode.cpp + ${INCROOT}/StencilMode.hpp ${SRCROOT}/Texture.cpp ${INCROOT}/Texture.hpp ${SRCROOT}/TextureSaver.cpp diff --git a/src/SFML/Graphics/GLExtensions.hpp b/src/SFML/Graphics/GLExtensions.hpp index a486f1507..a6400e25f 100644 --- a/src/SFML/Graphics/GLExtensions.hpp +++ b/src/SFML/Graphics/GLExtensions.hpp @@ -106,11 +106,13 @@ #define GLEXT_GL_FRAMEBUFFER GL_FRAMEBUFFER_OES #define GLEXT_GL_RENDERBUFFER GL_RENDERBUFFER_OES #define GLEXT_GL_DEPTH_COMPONENT GL_DEPTH_COMPONENT16_OES +#define GLEXT_GL_STENCIL_INDEX8 GL_STENCIL_INDEX8 #define GLEXT_GL_COLOR_ATTACHMENT0 GL_COLOR_ATTACHMENT0_OES #define GLEXT_GL_DEPTH_ATTACHMENT GL_DEPTH_ATTACHMENT_OES #define GLEXT_GL_FRAMEBUFFER_COMPLETE GL_FRAMEBUFFER_COMPLETE_OES #define GLEXT_GL_FRAMEBUFFER_BINDING GL_FRAMEBUFFER_BINDING_OES #define GLEXT_GL_INVALID_FRAMEBUFFER_OPERATION GL_INVALID_FRAMEBUFFER_OPERATION_OES +#define GLEXT_GL_STENCIL_ATTACHMENT GL_STENCIL_ATTACHMENT_OES // Core since 3.0 #define GLEXT_packed_depth_stencil SF_GLAD_GL_OES_packed_depth_stencil @@ -277,6 +279,7 @@ #define GLEXT_glGenerateMipmap glGenerateMipmapEXT #define GLEXT_GL_FRAMEBUFFER GL_FRAMEBUFFER_EXT #define GLEXT_GL_RENDERBUFFER GL_RENDERBUFFER_EXT +#define GLEXT_GL_STENCIL_INDEX8 GL_STENCIL_INDEX8_EXT #define GLEXT_GL_COLOR_ATTACHMENT0 GL_COLOR_ATTACHMENT0_EXT #define GLEXT_GL_DEPTH_ATTACHMENT GL_DEPTH_ATTACHMENT_EXT #define GLEXT_GL_FRAMEBUFFER_COMPLETE GL_FRAMEBUFFER_COMPLETE_EXT diff --git a/src/SFML/Graphics/RenderStates.cpp b/src/SFML/Graphics/RenderStates.cpp index 786430c25..e1decb9f5 100644 --- a/src/SFML/Graphics/RenderStates.cpp +++ b/src/SFML/Graphics/RenderStates.cpp @@ -56,6 +56,12 @@ RenderStates::RenderStates(const BlendMode& theBlendMode) : blendMode(theBlendMo } +//////////////////////////////////////////////////////////// +RenderStates::RenderStates(const StencilMode& theStencilMode) : stencilMode(theStencilMode) +{ +} + + //////////////////////////////////////////////////////////// RenderStates::RenderStates(const Texture* theTexture) : texture(theTexture) { @@ -69,12 +75,14 @@ RenderStates::RenderStates(const Shader* theShader) : shader(theShader) //////////////////////////////////////////////////////////// -RenderStates::RenderStates(const BlendMode& theBlendMode, - const Transform& theTransform, - CoordinateType theCoordinateType, - const Texture* theTexture, - const Shader* theShader) : +RenderStates::RenderStates(const BlendMode& theBlendMode, + const StencilMode& theStencilMode, + const Transform& theTransform, + CoordinateType theCoordinateType, + const Texture* theTexture, + const Shader* theShader) : blendMode(theBlendMode), +stencilMode(theStencilMode), transform(theTransform), coordinateType(theCoordinateType), texture(theTexture), diff --git a/src/SFML/Graphics/RenderTarget.cpp b/src/SFML/Graphics/RenderTarget.cpp index 8d9b2dfb5..c29da82ee 100644 --- a/src/SFML/Graphics/RenderTarget.cpp +++ b/src/SFML/Graphics/RenderTarget.cpp @@ -147,6 +147,50 @@ std::uint32_t equationToGlConstant(sf::BlendMode::Equation blendEquation) return GLEXT_GL_FUNC_ADD; } + + +// Convert an UpdateOperation constant to the corresponding OpenGL constant. +std::uint32_t stencilOperationToGlConstant(sf::StencilUpdateOperation operation) +{ + // clang-format off + switch (operation) + { + case sf::StencilUpdateOperation::Keep: return GL_KEEP; + case sf::StencilUpdateOperation::Zero: return GL_ZERO; + case sf::StencilUpdateOperation::Replace: return GL_REPLACE; + case sf::StencilUpdateOperation::Increment: return GL_INCR; + case sf::StencilUpdateOperation::Decrement: return GL_DECR; + case sf::StencilUpdateOperation::Invert: return GL_INVERT; + } + // clang-format on + + sf::err() << "Invalid value for sf::StencilUpdateOperation! Fallback to sf::StencilMode::Keep." << std::endl; + assert(false); + return GL_KEEP; +} + + +// Convert a Comparison constant to the corresponding OpenGL constant. +std::uint32_t stencilFunctionToGlConstant(sf::StencilComparison comparison) +{ + // clang-format off + switch (comparison) + { + case sf::StencilComparison::Never: return GL_NEVER; + case sf::StencilComparison::Less: return GL_LESS; + case sf::StencilComparison::LessEqual: return GL_LEQUAL; + case sf::StencilComparison::Greater: return GL_GREATER; + case sf::StencilComparison::GreaterEqual: return GL_GEQUAL; + case sf::StencilComparison::Equal: return GL_EQUAL; + case sf::StencilComparison::NotEqual: return GL_NOTEQUAL; + case sf::StencilComparison::Always: return GL_ALWAYS; + } + // clang-format on + + sf::err() << "Invalid value for sf::StencilComparison! Fallback to sf::StencilMode::Always." << std::endl; + assert(false); + return GL_ALWAYS; +} } // namespace RenderTargetImpl } // namespace @@ -171,6 +215,43 @@ void RenderTarget::clear(const Color& color) } +//////////////////////////////////////////////////////////// +void RenderTarget::clearStencil(StencilValue stencilValue) +{ + if (RenderTargetImpl::isActive(m_id) || setActive(true)) + { + // 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(glClearStencil(static_cast(stencilValue.value))); + glCheck(glClear(GL_STENCIL_BUFFER_BIT)); + } +} + + +//////////////////////////////////////////////////////////// +void RenderTarget::clear(const Color& color, StencilValue stencilValue) +{ + if (RenderTargetImpl::isActive(m_id) || setActive(true)) + { + // 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(glClearStencil(static_cast(stencilValue.value))); + glCheck(glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)); + } +} + + //////////////////////////////////////////////////////////// void RenderTarget::setView(const View& view) { @@ -519,6 +600,7 @@ void RenderTarget::resetGLStates() // Define the default OpenGL states glCheck(glDisable(GL_CULL_FACE)); glCheck(glDisable(GL_LIGHTING)); + glCheck(glDisable(GL_STENCIL_TEST)); glCheck(glDisable(GL_DEPTH_TEST)); glCheck(glDisable(GL_ALPHA_TEST)); glCheck(glDisable(GL_SCISSOR_TEST)); @@ -529,11 +611,14 @@ void RenderTarget::resetGLStates() glCheck(glEnableClientState(GL_VERTEX_ARRAY)); glCheck(glEnableClientState(GL_COLOR_ARRAY)); glCheck(glEnableClientState(GL_TEXTURE_COORD_ARRAY)); + glCheck(glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)); m_cache.scissorEnabled = false; + m_cache.stencilEnabled = false; m_cache.glStatesSet = true; // Apply the default SFML states applyBlendMode(BlendAlpha); + applyStencilMode(StencilMode()); applyTexture(nullptr); if (shaderAvailable) applyShader(nullptr); @@ -663,6 +748,43 @@ void RenderTarget::applyBlendMode(const BlendMode& mode) } +//////////////////////////////////////////////////////////// +void RenderTarget::applyStencilMode(const StencilMode& mode) +{ + using RenderTargetImpl::stencilFunctionToGlConstant; + using RenderTargetImpl::stencilOperationToGlConstant; + + // Fast path if we have a default (disabled) stencil mode + if (mode == StencilMode()) + { + if (m_cache.stencilEnabled) + { + glCheck(glDisable(GL_STENCIL_TEST)); + glCheck(glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)); + + m_cache.stencilEnabled = false; + } + } + else + { + // Apply the stencil mode + if (!m_cache.stencilEnabled) + glCheck(glEnable(GL_STENCIL_TEST)); + + glCheck(glStencilOp(GL_KEEP, + stencilOperationToGlConstant(mode.stencilUpdateOperation), + stencilOperationToGlConstant(mode.stencilUpdateOperation))); + glCheck(glStencilFunc(stencilFunctionToGlConstant(mode.stencilComparison), + static_cast(mode.stencilReference.value), + mode.stencilMask.value)); + + m_cache.stencilEnabled = true; + } + + m_cache.lastStencilMode = mode; +} + + //////////////////////////////////////////////////////////// void RenderTarget::applyTransform(const Transform& transform) { @@ -732,6 +854,14 @@ void RenderTarget::setupDraw(bool useVertexCache, const RenderStates& states) if (!m_cache.enable || (states.blendMode != m_cache.lastBlendMode)) applyBlendMode(states.blendMode); + // Apply the stencil mode + if (!m_cache.enable || (states.stencilMode != m_cache.lastStencilMode)) + applyStencilMode(states.stencilMode); + + // Mask the color buffer off if necessary + if (states.stencilMode.stencilOnly) + glCheck(glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)); + // Apply the texture if (!m_cache.enable || (states.texture && states.texture->m_fboAttachment)) { @@ -780,6 +910,10 @@ void RenderTarget::cleanupDraw(const RenderStates& states) if (states.texture && states.texture->m_fboAttachment) applyTexture(nullptr); + // Mask the color buffer back on if necessary + if (states.stencilMode.stencilOnly) + glCheck(glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)); + // Re-enable the cache at the end of the draw if it was disabled m_cache.enable = true; } diff --git a/src/SFML/Graphics/RenderTexture.cpp b/src/SFML/Graphics/RenderTexture.cpp index 1830b9856..45bff3029 100644 --- a/src/SFML/Graphics/RenderTexture.cpp +++ b/src/SFML/Graphics/RenderTexture.cpp @@ -85,7 +85,8 @@ bool RenderTexture::create(const Vector2u& size, const ContextSettings& settings } // Initialize the render texture - if (!m_impl->create(size, m_texture.m_texture, settings)) + // We pass the actual size of our texture since OpenGL ES requires that all attachments have identical sizes + if (!m_impl->create(m_texture.m_actualSize, m_texture.m_texture, settings)) return false; // We can now initialize the render target part diff --git a/src/SFML/Graphics/RenderTextureImplFBO.cpp b/src/SFML/Graphics/RenderTextureImplFBO.cpp index da42c04a9..e1d172b5d 100644 --- a/src/SFML/Graphics/RenderTextureImplFBO.cpp +++ b/src/SFML/Graphics/RenderTextureImplFBO.cpp @@ -153,9 +153,6 @@ bool RenderTextureImplFBO::create(const Vector2u& size, unsigned int textureId, if (settings.antialiasingLevel && !(GLEXT_framebuffer_multisample && GLEXT_framebuffer_blit)) return false; - if (settings.stencilBits && !GLEXT_packed_depth_stencil) - return false; - m_sRgb = settings.sRgbCapable && GL_EXT_texture_sRGB; #ifndef SFML_OPENGL_ES @@ -176,14 +173,17 @@ bool RenderTextureImplFBO::create(const Vector2u& size, unsigned int textureId, #endif - if (!settings.antialiasingLevel) { // Create the depth/stencil buffer if requested - if (settings.stencilBits) + if (settings.stencilBits && settings.depthBits) { - -#ifndef SFML_OPENGL_ES + if (!GLEXT_packed_depth_stencil) + { + err() << "Impossible to create render texture (combined depth/stencil buffer not supported)" + << std::endl; + return false; + } GLuint depthStencil = 0; glCheck(GLEXT_glGenRenderbuffers(1, &depthStencil)); @@ -200,17 +200,8 @@ bool RenderTextureImplFBO::create(const Vector2u& size, unsigned int textureId, static_cast(size.x), static_cast(size.y))); + m_depth = true; m_stencil = true; - -#else - - m_stencil = false; - - err() << "Impossible to create render texture (failed to create the attached depth/stencil buffer)" - << std::endl; - return false; - -#endif // SFML_OPENGL_ES } else if (settings.depthBits) { @@ -228,6 +219,29 @@ bool RenderTextureImplFBO::create(const Vector2u& size, unsigned int textureId, GLEXT_GL_DEPTH_COMPONENT, static_cast(size.x), static_cast(size.y))); + + m_depth = true; + m_stencil = false; + } + else if (settings.stencilBits) + { + GLuint depthStencil = 0; + glCheck(GLEXT_glGenRenderbuffers(1, &depthStencil)); + m_depthStencilBuffer = depthStencil; + if (!m_depthStencilBuffer) + { + err() << "Impossible to create render texture (failed to create the attached stencil buffer)" + << std::endl; + return false; + } + glCheck(GLEXT_glBindRenderbuffer(GLEXT_GL_RENDERBUFFER, m_depthStencilBuffer)); + glCheck(GLEXT_glRenderbufferStorage(GLEXT_GL_RENDERBUFFER, + GLEXT_GL_STENCIL_INDEX8, + static_cast(size.x), + static_cast(size.y))); + + m_depth = false; + m_stencil = true; } } else @@ -253,7 +267,7 @@ bool RenderTextureImplFBO::create(const Vector2u& size, unsigned int textureId, static_cast(size.y))); // Create the multisample depth/stencil buffer if requested - if (settings.stencilBits) + if (settings.stencilBits && settings.depthBits) { GLuint depthStencil = 0; glCheck(GLEXT_glGenRenderbuffers(1, &depthStencil)); @@ -272,6 +286,7 @@ bool RenderTextureImplFBO::create(const Vector2u& size, unsigned int textureId, static_cast(size.x), static_cast(size.y))); + m_depth = true; m_stencil = true; } else if (settings.depthBits) @@ -292,6 +307,31 @@ bool RenderTextureImplFBO::create(const Vector2u& size, unsigned int textureId, GLEXT_GL_DEPTH_COMPONENT, static_cast(size.x), static_cast(size.y))); + + m_depth = true; + m_stencil = false; + } + else if (settings.stencilBits) + { + GLuint depthStencil = 0; + glCheck(GLEXT_glGenRenderbuffers(1, &depthStencil)); + m_depthStencilBuffer = depthStencil; + if (!m_depthStencilBuffer) + { + err() << "Impossible to create render texture (failed to create the attached multisample " + "stencil buffer)" + << std::endl; + return false; + } + glCheck(GLEXT_glBindRenderbuffer(GLEXT_GL_RENDERBUFFER, m_depthStencilBuffer)); + glCheck(GLEXT_glRenderbufferStorageMultisample(GLEXT_GL_RENDERBUFFER, + static_cast(settings.antialiasingLevel), + GLEXT_GL_STENCIL_INDEX8, + static_cast(size.x), + static_cast(size.y))); + + m_depth = false; + m_stencil = true; } m_multisample = true; @@ -369,12 +409,13 @@ bool RenderTextureImplFBO::createFrameBuffer() // Link the depth/stencil renderbuffer to the frame buffer if (!m_multisample && m_depthStencilBuffer) { - glCheck(GLEXT_glFramebufferRenderbuffer(GLEXT_GL_FRAMEBUFFER, - GLEXT_GL_DEPTH_ATTACHMENT, - GLEXT_GL_RENDERBUFFER, - m_depthStencilBuffer)); - -#ifndef SFML_OPENGL_ES + if (m_depth) + { + glCheck(GLEXT_glFramebufferRenderbuffer(GLEXT_GL_FRAMEBUFFER, + GLEXT_GL_DEPTH_ATTACHMENT, + GLEXT_GL_RENDERBUFFER, + m_depthStencilBuffer)); + } if (m_stencil) { @@ -383,8 +424,6 @@ bool RenderTextureImplFBO::createFrameBuffer() GLEXT_GL_RENDERBUFFER, m_depthStencilBuffer)); } - -#endif } // Link the texture to the frame buffer @@ -429,10 +468,13 @@ bool RenderTextureImplFBO::createFrameBuffer() // Link the depth/stencil renderbuffer to the frame buffer if (m_depthStencilBuffer) { - glCheck(GLEXT_glFramebufferRenderbuffer(GLEXT_GL_FRAMEBUFFER, - GLEXT_GL_DEPTH_ATTACHMENT, - GLEXT_GL_RENDERBUFFER, - m_depthStencilBuffer)); + if (m_depth) + { + glCheck(GLEXT_glFramebufferRenderbuffer(GLEXT_GL_FRAMEBUFFER, + GLEXT_GL_DEPTH_ATTACHMENT, + GLEXT_GL_RENDERBUFFER, + m_depthStencilBuffer)); + } if (m_stencil) { diff --git a/src/SFML/Graphics/RenderTextureImplFBO.hpp b/src/SFML/Graphics/RenderTextureImplFBO.hpp index 2befeb41e..ca9466dc6 100644 --- a/src/SFML/Graphics/RenderTextureImplFBO.hpp +++ b/src/SFML/Graphics/RenderTextureImplFBO.hpp @@ -149,6 +149,7 @@ private: std::unique_ptr m_context; //!< Backup OpenGL context, used when none already exist unsigned int m_textureId{}; //!< The ID of the texture to attach to the FBO bool m_multisample{}; //!< Whether we have to create a multisample frame buffer as well + bool m_depth{}; //!< Whether we have depth attachment bool m_stencil{}; //!< Whether we have stencil attachment bool m_sRgb{}; //!< Whether we need to encode drawn pixels into sRGB color space }; diff --git a/src/SFML/Graphics/StencilMode.cpp b/src/SFML/Graphics/StencilMode.cpp new file mode 100644 index 000000000..b6a9ad254 --- /dev/null +++ b/src/SFML/Graphics/StencilMode.cpp @@ -0,0 +1,61 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2023 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include + + +namespace sf +{ +//////////////////////////////////////////////////////////// +StencilValue::StencilValue(int theValue) : value(static_cast(theValue)) +{ +} + + +//////////////////////////////////////////////////////////// +StencilValue::StencilValue(unsigned int theValue) : value(theValue) +{ +} + + +//////////////////////////////////////////////////////////// +bool operator==(const StencilMode& left, const StencilMode& right) +{ + return left.stencilUpdateOperation == right.stencilUpdateOperation && + left.stencilComparison == right.stencilComparison && + left.stencilReference.value == right.stencilReference.value && + left.stencilMask.value == right.stencilMask.value && left.stencilOnly == right.stencilOnly; +} + + +//////////////////////////////////////////////////////////// +bool operator!=(const StencilMode& left, const StencilMode& right) +{ + return !(left == right); +} + +} // namespace sf diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 2601b71a3..c12a430e7 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -92,6 +92,7 @@ set(GRAPHICS_SRC Graphics/Image.test.cpp Graphics/Rect.test.cpp Graphics/RectangleShape.test.cpp + Graphics/Render.test.cpp Graphics/RenderStates.test.cpp Graphics/RenderTarget.test.cpp Graphics/RenderTexture.test.cpp @@ -99,6 +100,7 @@ set(GRAPHICS_SRC Graphics/Shader.test.cpp Graphics/Shape.test.cpp Graphics/Sprite.test.cpp + Graphics/StencilMode.test.cpp Graphics/Text.test.cpp Graphics/Texture.test.cpp Graphics/Transform.test.cpp diff --git a/test/Graphics/Render.test.cpp b/test/Graphics/Render.test.cpp new file mode 100644 index 000000000..68ebcb1fc --- /dev/null +++ b/test/Graphics/Render.test.cpp @@ -0,0 +1,240 @@ +#include +#include +#include +#include + +#include + +#include + +TEST_CASE("[Graphics] Render Tests", runDisplayTests()) +{ + SECTION("Stencil Tests") + { + sf::RenderTexture renderTexture; + REQUIRE(renderTexture.create({100, 100}, sf::ContextSettings(0, 8)) == true); + renderTexture.clear(sf::Color::Red, 127); + + sf::RectangleShape shape1({100, 100}); + shape1.setFillColor(sf::Color::Green); + sf::RectangleShape shape2({100, 100}); + shape2.setFillColor(sf::Color::Blue); + + SECTION("Stencil-Only") + { + renderTexture.draw(shape1, + sf::StencilMode{sf::StencilComparison::Always, sf::StencilUpdateOperation::Keep, 1, 0xFF, true}); + renderTexture.display(); + CHECK(renderTexture.getTexture().copyToImage().getPixel({50, 50}) == sf::Color::Red); + } + + SECTION("Comparisons") + { + SECTION("Always") + { + renderTexture + .draw(shape1, + sf::StencilMode{sf::StencilComparison::Always, sf::StencilUpdateOperation::Keep, 1, 0xFF, false}); + renderTexture.display(); + CHECK(renderTexture.getTexture().copyToImage().getPixel({50, 50}) == sf::Color::Green); + } + + SECTION("Equal") + { + renderTexture + .draw(shape1, + sf::StencilMode{sf::StencilComparison::Equal, sf::StencilUpdateOperation::Keep, 126, 0xFF, false}); + renderTexture.display(); + CHECK(renderTexture.getTexture().copyToImage().getPixel({50, 50}) == sf::Color::Red); + renderTexture + .draw(shape1, + sf::StencilMode{sf::StencilComparison::Equal, sf::StencilUpdateOperation::Keep, 127, 0xFF, false}); + renderTexture.display(); + CHECK(renderTexture.getTexture().copyToImage().getPixel({50, 50}) == sf::Color::Green); + } + + SECTION("Greater") + { + renderTexture + .draw(shape1, + sf::StencilMode{sf::StencilComparison::Greater, sf::StencilUpdateOperation::Keep, 126, 0xFF, false}); + renderTexture.display(); + CHECK(renderTexture.getTexture().copyToImage().getPixel({50, 50}) == sf::Color::Red); + renderTexture + .draw(shape1, + sf::StencilMode{sf::StencilComparison::Greater, sf::StencilUpdateOperation::Keep, 127, 0xFF, false}); + renderTexture.display(); + CHECK(renderTexture.getTexture().copyToImage().getPixel({50, 50}) == sf::Color::Red); + renderTexture + .draw(shape1, + sf::StencilMode{sf::StencilComparison::Greater, sf::StencilUpdateOperation::Keep, 128, 0xFF, false}); + renderTexture.display(); + CHECK(renderTexture.getTexture().copyToImage().getPixel({50, 50}) == sf::Color::Green); + } + + SECTION("GreaterEqual") + { + renderTexture.draw(shape1, + sf::StencilMode{sf::StencilComparison::GreaterEqual, + sf::StencilUpdateOperation::Keep, + 126, + 0xFF, + false}); + renderTexture.display(); + CHECK(renderTexture.getTexture().copyToImage().getPixel({50, 50}) == sf::Color::Red); + renderTexture.draw(shape1, + sf::StencilMode{sf::StencilComparison::GreaterEqual, + sf::StencilUpdateOperation::Keep, + 127, + 0xFF, + false}); + renderTexture.display(); + CHECK(renderTexture.getTexture().copyToImage().getPixel({50, 50}) == sf::Color::Green); + renderTexture.draw(shape2, + sf::StencilMode{sf::StencilComparison::GreaterEqual, + sf::StencilUpdateOperation::Keep, + 128, + 0xFF, + false}); + renderTexture.display(); + CHECK(renderTexture.getTexture().copyToImage().getPixel({50, 50}) == sf::Color::Blue); + } + + SECTION("Less") + { + renderTexture + .draw(shape1, + sf::StencilMode{sf::StencilComparison::Less, sf::StencilUpdateOperation::Keep, 128, 0xFF, false}); + renderTexture.display(); + CHECK(renderTexture.getTexture().copyToImage().getPixel({50, 50}) == sf::Color::Red); + renderTexture + .draw(shape1, + sf::StencilMode{sf::StencilComparison::Less, sf::StencilUpdateOperation::Keep, 127, 0xFF, false}); + renderTexture.display(); + CHECK(renderTexture.getTexture().copyToImage().getPixel({50, 50}) == sf::Color::Red); + renderTexture + .draw(shape1, + sf::StencilMode{sf::StencilComparison::Less, sf::StencilUpdateOperation::Keep, 126, 0xFF, false}); + renderTexture.display(); + CHECK(renderTexture.getTexture().copyToImage().getPixel({50, 50}) == sf::Color::Green); + } + + SECTION("LessEqual") + { + renderTexture + .draw(shape1, + sf::StencilMode{sf::StencilComparison::LessEqual, sf::StencilUpdateOperation::Keep, 128, 0xFF, false}); + renderTexture.display(); + CHECK(renderTexture.getTexture().copyToImage().getPixel({50, 50}) == sf::Color::Red); + renderTexture + .draw(shape1, + sf::StencilMode{sf::StencilComparison::LessEqual, sf::StencilUpdateOperation::Keep, 127, 0xFF, false}); + renderTexture.display(); + CHECK(renderTexture.getTexture().copyToImage().getPixel({50, 50}) == sf::Color::Green); + renderTexture + .draw(shape2, + sf::StencilMode{sf::StencilComparison::LessEqual, sf::StencilUpdateOperation::Keep, 126, 0xFF, false}); + renderTexture.display(); + CHECK(renderTexture.getTexture().copyToImage().getPixel({50, 50}) == sf::Color::Blue); + } + + SECTION("Never") + { + renderTexture + .draw(shape1, + sf::StencilMode{sf::StencilComparison::Never, sf::StencilUpdateOperation::Keep, 127, 0xFF, false}); + renderTexture.display(); + CHECK(renderTexture.getTexture().copyToImage().getPixel({50, 50}) == sf::Color::Red); + } + + SECTION("NotEqual") + { + renderTexture + .draw(shape1, + sf::StencilMode{sf::StencilComparison::NotEqual, sf::StencilUpdateOperation::Keep, 127, 0xFF, false}); + renderTexture.display(); + CHECK(renderTexture.getTexture().copyToImage().getPixel({50, 50}) == sf::Color::Red); + renderTexture + .draw(shape1, + sf::StencilMode{sf::StencilComparison::NotEqual, sf::StencilUpdateOperation::Keep, 128, 0xFF, false}); + renderTexture.display(); + CHECK(renderTexture.getTexture().copyToImage().getPixel({50, 50}) == sf::Color::Green); + } + } + + SECTION("Updating") + { + SECTION("Decrement") + { + renderTexture + .draw(shape1, + sf::StencilMode{sf::StencilComparison::Always, sf::StencilUpdateOperation::Decrement, 127, 0xFF, true}); + renderTexture + .draw(shape1, + sf::StencilMode{sf::StencilComparison::Equal, sf::StencilUpdateOperation::Decrement, 126, 0xFF, false}); + renderTexture.display(); + CHECK(renderTexture.getTexture().copyToImage().getPixel({50, 50}) == sf::Color::Green); + } + + SECTION("Increment") + { + renderTexture + .draw(shape1, + sf::StencilMode{sf::StencilComparison::Always, sf::StencilUpdateOperation::Increment, 127, 0xFF, true}); + renderTexture + .draw(shape1, + sf::StencilMode{sf::StencilComparison::Equal, sf::StencilUpdateOperation::Increment, 128, 0xFF, false}); + renderTexture.display(); + CHECK(renderTexture.getTexture().copyToImage().getPixel({50, 50}) == sf::Color::Green); + } + + SECTION("Invert") + { + renderTexture + .draw(shape1, + sf::StencilMode{sf::StencilComparison::Always, sf::StencilUpdateOperation::Invert, 127, 0xFF, true}); + renderTexture + .draw(shape1, + sf::StencilMode{sf::StencilComparison::Equal, sf::StencilUpdateOperation::Invert, 0x80, 0xFF, false}); + renderTexture.display(); + CHECK(renderTexture.getTexture().copyToImage().getPixel({50, 50}) == sf::Color::Green); + } + + SECTION("Keep") + { + renderTexture + .draw(shape1, + sf::StencilMode{sf::StencilComparison::Always, sf::StencilUpdateOperation::Keep, 127, 0xFF, true}); + renderTexture + .draw(shape1, + sf::StencilMode{sf::StencilComparison::Equal, sf::StencilUpdateOperation::Keep, 127, 0xFF, false}); + renderTexture.display(); + CHECK(renderTexture.getTexture().copyToImage().getPixel({50, 50}) == sf::Color::Green); + } + + SECTION("Replace") + { + renderTexture + .draw(shape1, + sf::StencilMode{sf::StencilComparison::Always, sf::StencilUpdateOperation::Replace, 255, 0xFF, true}); + renderTexture + .draw(shape1, + sf::StencilMode{sf::StencilComparison::Equal, sf::StencilUpdateOperation::Replace, 255, 0xFF, false}); + renderTexture.display(); + CHECK(renderTexture.getTexture().copyToImage().getPixel({50, 50}) == sf::Color::Green); + } + + SECTION("Zero") + { + renderTexture + .draw(shape1, + sf::StencilMode{sf::StencilComparison::Always, sf::StencilUpdateOperation::Zero, 127, 0xFF, true}); + renderTexture + .draw(shape1, + sf::StencilMode{sf::StencilComparison::Equal, sf::StencilUpdateOperation::Zero, 0, 0xFF, false}); + renderTexture.display(); + CHECK(renderTexture.getTexture().copyToImage().getPixel({50, 50}) == sf::Color::Green); + } + } + } +} diff --git a/test/Graphics/RenderStates.test.cpp b/test/Graphics/RenderStates.test.cpp index ce02040d9..de9c3be62 100644 --- a/test/Graphics/RenderStates.test.cpp +++ b/test/Graphics/RenderStates.test.cpp @@ -21,6 +21,7 @@ TEST_CASE("[Graphics] sf::RenderStates") { const sf::RenderStates renderStates; CHECK(renderStates.blendMode == sf::BlendMode()); + CHECK(renderStates.stencilMode == sf::StencilMode{}); CHECK(renderStates.transform == sf::Transform()); CHECK(renderStates.coordinateType == sf::CoordinateType::Pixels); CHECK(renderStates.texture == nullptr); @@ -37,17 +38,30 @@ TEST_CASE("[Graphics] sf::RenderStates") sf::BlendMode::Equation::Max); const sf::RenderStates renderStates(blendMode); CHECK(renderStates.blendMode == blendMode); + CHECK(renderStates.stencilMode == sf::StencilMode{}); CHECK(renderStates.transform == sf::Transform()); CHECK(renderStates.coordinateType == sf::CoordinateType::Pixels); CHECK(renderStates.texture == nullptr); CHECK(renderStates.shader == nullptr); } + SECTION("StencilMode constructor") + { + const sf::StencilMode stencilMode{sf::StencilComparison::Equal, sf::StencilUpdateOperation::Replace, 1, 0u, true}; + const sf::RenderStates renderStates(stencilMode); + CHECK(renderStates.blendMode == sf::BlendMode()); + CHECK(renderStates.stencilMode == stencilMode); + CHECK(renderStates.transform == sf::Transform()); + CHECK(renderStates.texture == nullptr); + CHECK(renderStates.shader == nullptr); + } + SECTION("Transform constructor") { const sf::Transform transform(10, 9, 8, 7, 6, 5, 4, 3, 2); const sf::RenderStates renderStates(transform); CHECK(renderStates.blendMode == sf::BlendMode()); + CHECK(renderStates.stencilMode == sf::StencilMode{}); CHECK(renderStates.transform == transform); CHECK(renderStates.coordinateType == sf::CoordinateType::Pixels); CHECK(renderStates.texture == nullptr); @@ -59,6 +73,7 @@ TEST_CASE("[Graphics] sf::RenderStates") const sf::Texture* texture = nullptr; const sf::RenderStates renderStates(texture); CHECK(renderStates.blendMode == sf::BlendMode()); + CHECK(renderStates.stencilMode == sf::StencilMode{}); CHECK(renderStates.transform == sf::Transform()); CHECK(renderStates.coordinateType == sf::CoordinateType::Pixels); CHECK(renderStates.texture == texture); @@ -70,6 +85,7 @@ TEST_CASE("[Graphics] sf::RenderStates") const sf::Shader* shader = nullptr; const sf::RenderStates renderStates(shader); CHECK(renderStates.blendMode == sf::BlendMode()); + CHECK(renderStates.stencilMode == sf::StencilMode{}); CHECK(renderStates.transform == sf::Transform()); CHECK(renderStates.coordinateType == sf::CoordinateType::Pixels); CHECK(renderStates.texture == nullptr); @@ -78,15 +94,17 @@ TEST_CASE("[Graphics] sf::RenderStates") SECTION("Verbose constructor") { - const sf::BlendMode blendMode(sf::BlendMode::Factor::One, + const sf::BlendMode blendMode(sf::BlendMode::Factor::One, sf::BlendMode::Factor::SrcColor, sf::BlendMode::Equation::ReverseSubtract, sf::BlendMode::Factor::OneMinusDstAlpha, sf::BlendMode::Factor::DstAlpha, sf::BlendMode::Equation::Max); - const sf::Transform transform(10, 2, 3, 4, 50, 40, 30, 20, 10); - const sf::RenderStates renderStates(blendMode, transform, sf::CoordinateType::Normalized, nullptr, nullptr); + const sf::StencilMode stencilMode{sf::StencilComparison::Equal, sf::StencilUpdateOperation::Replace, 1, 0u, true}; + const sf::Transform transform(10, 2, 3, 4, 50, 40, 30, 20, 10); + const sf::RenderStates renderStates(blendMode, stencilMode, transform, sf::CoordinateType::Normalized, nullptr, nullptr); CHECK(renderStates.blendMode == blendMode); + CHECK(renderStates.stencilMode == stencilMode); CHECK(renderStates.transform == transform); CHECK(renderStates.coordinateType == sf::CoordinateType::Normalized); CHECK(renderStates.texture == nullptr); @@ -97,6 +115,7 @@ TEST_CASE("[Graphics] sf::RenderStates") SECTION("Default constant") { CHECK(sf::RenderStates::Default.blendMode == sf::BlendMode()); + CHECK(sf::RenderStates::Default.stencilMode == sf::StencilMode{}); CHECK(sf::RenderStates::Default.transform == sf::Transform()); CHECK(sf::RenderStates::Default.coordinateType == sf::CoordinateType::Pixels); CHECK(sf::RenderStates::Default.texture == nullptr); diff --git a/test/Graphics/RenderTexture.test.cpp b/test/Graphics/RenderTexture.test.cpp index 0007bb1ab..6d37448c8 100644 --- a/test/Graphics/RenderTexture.test.cpp +++ b/test/Graphics/RenderTexture.test.cpp @@ -32,6 +32,10 @@ TEST_CASE("[Graphics] sf::RenderTexture", runDisplayTests()) CHECK(!renderTexture.isRepeated()); CHECK(renderTexture.getSize() == sf::Vector2u(480, 360)); CHECK(!renderTexture.isSrgb()); + CHECK(renderTexture.create({360, 480})); + CHECK(renderTexture.getSize() == sf::Vector2u(360, 480)); + CHECK(renderTexture.create({100, 100}, sf::ContextSettings(8, 0))); + CHECK(renderTexture.create({100, 100}, sf::ContextSettings(0, 8))); } SECTION("getMaximumAntialiasingLevel()") diff --git a/test/Graphics/StencilMode.test.cpp b/test/Graphics/StencilMode.test.cpp new file mode 100644 index 000000000..9086b6987 --- /dev/null +++ b/test/Graphics/StencilMode.test.cpp @@ -0,0 +1,71 @@ +#include + +#include + +#include + +TEST_CASE("[Graphics] sf::StencilMode") +{ + SECTION("Type traits") + { + STATIC_CHECK(std::is_copy_constructible_v); + STATIC_CHECK(std::is_copy_assignable_v); + STATIC_CHECK(std::is_nothrow_move_constructible_v); + STATIC_CHECK(std::is_nothrow_move_assignable_v); + } + + SECTION("Construction") + { + const sf::StencilMode stencilMode; + CHECK(stencilMode.stencilComparison == sf::StencilComparison::Always); + CHECK(stencilMode.stencilUpdateOperation == sf::StencilUpdateOperation::Keep); + CHECK(stencilMode.stencilReference.value == 0u); + CHECK(stencilMode.stencilMask.value == ~0u); + CHECK(stencilMode.stencilOnly == false); + } + + SECTION("Stencil value type traits") + { + STATIC_CHECK(!std::is_default_constructible_v); + STATIC_CHECK(!std::is_convertible_v); + STATIC_CHECK(!std::is_convertible_v); + STATIC_CHECK(!std::is_convertible_v); + STATIC_CHECK(!std::is_convertible_v); + STATIC_CHECK(!std::is_convertible_v); + STATIC_CHECK(std::is_convertible_v); + STATIC_CHECK(std::is_convertible_v); + } + + SECTION("Stencil value construction") + { + const sf::StencilValue stencilValue{0}; + CHECK(stencilValue.value == 0u); + } + + SECTION("Operators") + { + SECTION("operator==") + { + CHECK(sf::StencilMode{} == sf::StencilMode{}); + CHECK(sf::StencilMode{sf::StencilComparison::Equal, sf::StencilUpdateOperation::Replace, 1, 0u, true} == + sf::StencilMode{sf::StencilComparison::Equal, sf::StencilUpdateOperation::Replace, 1, 0u, true}); + + CHECK_FALSE(sf::StencilMode{} == + sf::StencilMode{sf::StencilComparison::Equal, sf::StencilUpdateOperation::Replace, 1, 0u, true}); + CHECK_FALSE(sf::StencilMode{sf::StencilComparison::Greater, sf::StencilUpdateOperation::Invert, 0, ~0u, false} == + sf::StencilMode{sf::StencilComparison::Equal, sf::StencilUpdateOperation::Replace, 1, 0u, true}); + } + + SECTION("operator!=") + { + CHECK_FALSE(sf::StencilMode{} != sf::StencilMode{}); + CHECK_FALSE(sf::StencilMode{sf::StencilComparison::Equal, sf::StencilUpdateOperation::Replace, 1, 0u, true} != + sf::StencilMode{sf::StencilComparison::Equal, sf::StencilUpdateOperation::Replace, 1, 0u, true}); + + CHECK(sf::StencilMode{} != + sf::StencilMode{sf::StencilComparison::Equal, sf::StencilUpdateOperation::Replace, 1, 0u, true}); + CHECK(sf::StencilMode{sf::StencilComparison::Greater, sf::StencilUpdateOperation::Invert, 0, ~0u, false} != + sf::StencilMode{sf::StencilComparison::Equal, sf::StencilUpdateOperation::Replace, 1, 0u, true}); + } + } +} diff --git a/test/TestUtilities/GraphicsUtil.cpp b/test/TestUtilities/GraphicsUtil.cpp index 3f93fe63c..b5eaa56be 100644 --- a/test/TestUtilities/GraphicsUtil.cpp +++ b/test/TestUtilities/GraphicsUtil.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -17,6 +18,59 @@ std::ostream& operator<<(std::ostream& os, const BlendMode& blendMode) << ", " << static_cast(blendMode.alphaEquation) << " )"; } +std::ostream& operator<<(std::ostream& os, const StencilComparison& comparison) +{ + switch (comparison) + { + case StencilComparison::Never: + return os << "Never"; + case StencilComparison::Less: + return os << "Less"; + case StencilComparison::LessEqual: + return os << "LessEqual"; + case StencilComparison::Greater: + return os << "Greater"; + case StencilComparison::GreaterEqual: + return os << "GreaterEqual"; + case StencilComparison::Equal: + return os << "Equal"; + case StencilComparison::NotEqual: + return os << "NotEqual"; + case StencilComparison::Always: + return os << "Always"; + } + + return os; +} + +std::ostream& operator<<(std::ostream& os, const StencilUpdateOperation& updateOperation) +{ + switch (updateOperation) + { + case StencilUpdateOperation::Keep: + return os << "Keep"; + case StencilUpdateOperation::Zero: + return os << "Zero"; + case StencilUpdateOperation::Replace: + return os << "Replace"; + case StencilUpdateOperation::Increment: + return os << "Increment"; + case StencilUpdateOperation::Decrement: + return os << "Decrement"; + case StencilUpdateOperation::Invert: + return os << "Invert"; + } + + return os; +} + +std::ostream& operator<<(std::ostream& os, const StencilMode& stencilMode) +{ + return os << "( " << stencilMode.stencilComparison << ", " << stencilMode.stencilUpdateOperation << ", " + << stencilMode.stencilReference.value << ", " << stencilMode.stencilMask.value << ", " + << stencilMode.stencilOnly << " )"; +} + std::ostream& operator<<(std::ostream& os, const Color& color) { return os << "0x" << std::hex << color.toInteger() << std::dec << " (r=" << int{color.r} << ", g=" << int{color.g} diff --git a/test/TestUtilities/GraphicsUtil.hpp b/test/TestUtilities/GraphicsUtil.hpp index 79d6b6530..1ce0c2bbc 100644 --- a/test/TestUtilities/GraphicsUtil.hpp +++ b/test/TestUtilities/GraphicsUtil.hpp @@ -12,6 +12,7 @@ namespace sf { struct BlendMode; +struct StencilMode; class Color; class Transform; @@ -19,6 +20,7 @@ template class Rect; std::ostream& operator<<(std::ostream& os, const BlendMode& blendMode); +std::ostream& operator<<(std::ostream& os, const StencilMode& stencilMode); std::ostream& operator<<(std::ostream& os, const Color& color); std::ostream& operator<<(std::ostream& os, const Transform& transform);