Added support for stencil testing.

Co-authored-by: binary1248 <binary1248@hotmail.com>
Co-authored-by: Chris Thrasher <chrisjthrasher@gmail.com>
This commit is contained in:
Kipernal 2015-04-28 23:28:02 -04:00 committed by Chris Thrasher
parent 9022d9564d
commit eb07e1e6c5
23 changed files with 1141 additions and 46 deletions

View File

@ -20,6 +20,7 @@ endif()
if(SFML_BUILD_GRAPHICS)
add_subdirectory(opengl)
add_subdirectory(stencil)
if(NOT SFML_OS_IOS)
add_subdirectory(joystick)

View File

@ -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)

View File

@ -0,0 +1,138 @@
////////////////////////////////////////////////////////////
// Headers
////////////////////////////////////////////////////////////
#include <SFML/Graphics.hpp>
////////////////////////////////////////////////////////////
/// 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;
}

View File

@ -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}

View File

@ -46,6 +46,7 @@
#include <SFML/Graphics/Shader.hpp>
#include <SFML/Graphics/Shape.hpp>
#include <SFML/Graphics/Sprite.hpp>
#include <SFML/Graphics/StencilMode.hpp>
#include <SFML/Graphics/Text.hpp>
#include <SFML/Graphics/Texture.hpp>
#include <SFML/Graphics/Transform.hpp>

View File

@ -31,6 +31,7 @@
#include <SFML/Graphics/BlendMode.hpp>
#include <SFML/Graphics/CoordinateType.hpp>
#include <SFML/Graphics/StencilMode.hpp>
#include <SFML/Graphics/Transform.hpp>
@ -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

View File

@ -35,6 +35,7 @@
#include <SFML/Graphics/PrimitiveType.hpp>
#include <SFML/Graphics/Rect.hpp>
#include <SFML/Graphics/RenderStates.hpp>
#include <SFML/Graphics/StencilMode.hpp>
#include <SFML/Graphics/Vertex.hpp>
#include <SFML/Graphics/View.hpp>
@ -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?

View File

@ -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 <SFML/Graphics/Export.hpp>
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 <typename T>
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
///
////////////////////////////////////////////////////////////

View File

@ -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

View File

@ -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

View File

@ -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),

View File

@ -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<int>(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<int>(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<int>(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;
}

View File

@ -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

View File

@ -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<GLsizei>(size.x),
static_cast<GLsizei>(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<GLsizei>(size.x),
static_cast<GLsizei>(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<GLsizei>(size.x),
static_cast<GLsizei>(size.y)));
m_depth = false;
m_stencil = true;
}
}
else
@ -253,7 +267,7 @@ bool RenderTextureImplFBO::create(const Vector2u& size, unsigned int textureId,
static_cast<GLsizei>(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<GLsizei>(size.x),
static_cast<GLsizei>(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<GLsizei>(size.x),
static_cast<GLsizei>(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<GLsizei>(settings.antialiasingLevel),
GLEXT_GL_STENCIL_INDEX8,
static_cast<GLsizei>(size.x),
static_cast<GLsizei>(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)
{

View File

@ -149,6 +149,7 @@ private:
std::unique_ptr<Context> 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
};

View File

@ -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 <SFML/Graphics/StencilMode.hpp>
namespace sf
{
////////////////////////////////////////////////////////////
StencilValue::StencilValue(int theValue) : value(static_cast<unsigned int>(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

View File

@ -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

View File

@ -0,0 +1,240 @@
#include <SFML/Graphics/Image.hpp>
#include <SFML/Graphics/RectangleShape.hpp>
#include <SFML/Graphics/RenderTexture.hpp>
#include <SFML/Graphics/StencilMode.hpp>
#include <catch2/catch_test_macros.hpp>
#include <GraphicsUtil.hpp>
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);
}
}
}
}

View File

@ -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);

View File

@ -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()")

View File

@ -0,0 +1,71 @@
#include <SFML/Graphics/StencilMode.hpp>
#include <catch2/catch_test_macros.hpp>
#include <type_traits>
TEST_CASE("[Graphics] sf::StencilMode")
{
SECTION("Type traits")
{
STATIC_CHECK(std::is_copy_constructible_v<sf::StencilMode>);
STATIC_CHECK(std::is_copy_assignable_v<sf::StencilMode>);
STATIC_CHECK(std::is_nothrow_move_constructible_v<sf::StencilMode>);
STATIC_CHECK(std::is_nothrow_move_assignable_v<sf::StencilMode>);
}
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<sf::StencilValue>);
STATIC_CHECK(!std::is_convertible_v<bool, sf::StencilValue>);
STATIC_CHECK(!std::is_convertible_v<char, sf::StencilValue>);
STATIC_CHECK(!std::is_convertible_v<unsigned char, sf::StencilValue>);
STATIC_CHECK(!std::is_convertible_v<short, sf::StencilValue>);
STATIC_CHECK(!std::is_convertible_v<unsigned short, sf::StencilValue>);
STATIC_CHECK(std::is_convertible_v<int, sf::StencilValue>);
STATIC_CHECK(std::is_convertible_v<unsigned int, sf::StencilValue>);
}
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});
}
}
}

View File

@ -1,6 +1,7 @@
#include <SFML/Graphics/BlendMode.hpp>
#include <SFML/Graphics/Color.hpp>
#include <SFML/Graphics/Rect.hpp>
#include <SFML/Graphics/StencilMode.hpp>
#include <SFML/Graphics/Transform.hpp>
#include <GraphicsUtil.hpp>
@ -17,6 +18,59 @@ std::ostream& operator<<(std::ostream& os, const BlendMode& blendMode)
<< ", " << static_cast<int>(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}

View File

@ -12,6 +12,7 @@
namespace sf
{
struct BlendMode;
struct StencilMode;
class Color;
class Transform;
@ -19,6 +20,7 @@ template <typename>
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);