//////////////////////////////////////////////////////////// // // SFML - Simple and Fast Multimedia Library // Copyright (C) 2007-2025 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { // A nested named namespace is used here to allow unity builds of SFML. namespace RenderTargetImpl { // Mutex to protect ID generation and our context-RenderTarget-map std::recursive_mutex& getMutex() { static std::recursive_mutex mutex; return mutex; } // Unique identifier, used for identifying RenderTargets when // tracking the currently active RenderTarget within a given context std::uint64_t getUniqueId() { const std::lock_guard lock(getMutex()); static std::uint64_t id = 1; // start at 1, zero is "no RenderTarget" return id++; } // Map to help us detect whether a different RenderTarget // has been activated within a single context using ContextRenderTargetMap = std::unordered_map; ContextRenderTargetMap& getContextRenderTargetMap() { static ContextRenderTargetMap contextRenderTargetMap; return contextRenderTargetMap; } // Check if a RenderTarget with the given ID is active in the current context bool isActive(std::uint64_t id) { const auto it = getContextRenderTargetMap().find(sf::Context::getActiveContextId()); return (it != getContextRenderTargetMap().end()) && (it->second == id); } // Convert an sf::BlendMode::Factor constant to the corresponding OpenGL constant. std::uint32_t factorToGlConstant(sf::BlendMode::Factor blendFactor) { // clang-format off switch (blendFactor) { case sf::BlendMode::Factor::Zero: return GL_ZERO; case sf::BlendMode::Factor::One: return GL_ONE; case sf::BlendMode::Factor::SrcColor: return GL_SRC_COLOR; case sf::BlendMode::Factor::OneMinusSrcColor: return GL_ONE_MINUS_SRC_COLOR; case sf::BlendMode::Factor::DstColor: return GL_DST_COLOR; case sf::BlendMode::Factor::OneMinusDstColor: return GL_ONE_MINUS_DST_COLOR; case sf::BlendMode::Factor::SrcAlpha: return GL_SRC_ALPHA; case sf::BlendMode::Factor::OneMinusSrcAlpha: return GL_ONE_MINUS_SRC_ALPHA; case sf::BlendMode::Factor::DstAlpha: return GL_DST_ALPHA; case sf::BlendMode::Factor::OneMinusDstAlpha: return GL_ONE_MINUS_DST_ALPHA; } // clang-format on sf::err() << "Invalid value for sf::BlendMode::Factor! Fallback to sf::BlendMode::Factor::Zero." << std::endl; assert(false); return GL_ZERO; } // Convert an sf::BlendMode::Equation constant to the corresponding OpenGL constant. std::uint32_t equationToGlConstant(sf::BlendMode::Equation blendEquation) { switch (blendEquation) { case sf::BlendMode::Equation::Add: return GLEXT_GL_FUNC_ADD; case sf::BlendMode::Equation::Subtract: if (GLEXT_blend_subtract) return GLEXT_GL_FUNC_SUBTRACT; break; case sf::BlendMode::Equation::ReverseSubtract: if (GLEXT_blend_subtract) return GLEXT_GL_FUNC_REVERSE_SUBTRACT; break; case sf::BlendMode::Equation::Min: if (GLEXT_blend_minmax) return GLEXT_GL_MIN; break; case sf::BlendMode::Equation::Max: if (GLEXT_blend_minmax) return GLEXT_GL_MAX; break; } static bool warned = false; if (!warned) { sf::err() << "OpenGL extension EXT_blend_minmax or EXT_blend_subtract unavailable" << '\n' << "Some blending equations will fallback to sf::BlendMode::Equation::Add" << '\n' << "Ensure that hardware acceleration is enabled if available" << std::endl; warned = true; } 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 namespace sf { //////////////////////////////////////////////////////////// void RenderTarget::clear(Color color) { 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(glClear(GL_COLOR_BUFFER_BIT)); } } //////////////////////////////////////////////////////////// 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(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) { m_view = view; m_cache.viewChanged = true; } //////////////////////////////////////////////////////////// const View& RenderTarget::getView() const { return m_view; } //////////////////////////////////////////////////////////// const View& RenderTarget::getDefaultView() const { return m_defaultView; } //////////////////////////////////////////////////////////// IntRect RenderTarget::getViewport(const View& view) const { const auto [width, height] = Vector2f(getSize()); const FloatRect& viewport = view.getViewport(); return IntRect(Rect({std::lround(width * viewport.position.x), std::lround(height * viewport.position.y)}, {std::lround(width * viewport.size.x), std::lround(height * viewport.size.y)})); } //////////////////////////////////////////////////////////// IntRect RenderTarget::getScissor(const View& view) const { const auto [width, height] = Vector2f(getSize()); const FloatRect& scissor = view.getScissor(); return IntRect(Rect({std::lround(width * scissor.position.x), std::lround(height * scissor.position.y)}, {std::lround(width * scissor.size.x), std::lround(height * scissor.size.y)})); } //////////////////////////////////////////////////////////// Vector2f RenderTarget::mapPixelToCoords(Vector2i point) const { return mapPixelToCoords(point, getView()); } //////////////////////////////////////////////////////////// Vector2f RenderTarget::mapPixelToCoords(Vector2i point, const View& view) const { // First, convert from viewport coordinates to homogeneous coordinates const FloatRect viewport = FloatRect(getViewport(view)); const Vector2f normalized = Vector2f(-1, 1) + Vector2f(2, -2).componentWiseMul(Vector2f(point) - viewport.position).componentWiseDiv(viewport.size); // Then transform by the inverse of the view matrix return view.getInverseTransform().transformPoint(normalized); } //////////////////////////////////////////////////////////// Vector2i RenderTarget::mapCoordsToPixel(Vector2f point) const { return mapCoordsToPixel(point, getView()); } //////////////////////////////////////////////////////////// Vector2i RenderTarget::mapCoordsToPixel(Vector2f point, const View& view) const { // First, transform the point by the view matrix const Vector2f normalized = view.getTransform().transformPoint(point); // Then convert to viewport coordinates const FloatRect viewport = FloatRect(getViewport(view)); return Vector2i( (normalized.componentWiseMul({1, -1}) + sf::Vector2f(1, 1)).componentWiseDiv({2, 2}).componentWiseMul(viewport.size) + viewport.position); } //////////////////////////////////////////////////////////// void RenderTarget::draw(const Drawable& drawable, const RenderStates& states) { drawable.draw(*this, states); } //////////////////////////////////////////////////////////// void RenderTarget::draw(const Vertex* vertices, std::size_t vertexCount, PrimitiveType type, const RenderStates& states) { // Nothing to draw? if (!vertices || (vertexCount == 0)) return; if (RenderTargetImpl::isActive(m_id) || setActive(true)) { // Check if the vertex count is low enough so that we can pre-transform them const bool useVertexCache = (vertexCount <= m_cache.vertexCache.size()); if (useVertexCache) { // Pre-transform the vertices and store them into the vertex cache for (std::size_t i = 0; i < vertexCount; ++i) { Vertex& vertex = m_cache.vertexCache[i]; vertex.position = states.transform * vertices[i].position; vertex.color = vertices[i].color; vertex.texCoords = vertices[i].texCoords; } } setupDraw(useVertexCache, states); // Check if texture coordinates array is needed, and update client state accordingly const bool enableTexCoordsArray = (states.texture || states.shader); if (!m_cache.enable || (enableTexCoordsArray != m_cache.texCoordsArrayEnabled)) { if (enableTexCoordsArray) glCheck(glEnableClientState(GL_TEXTURE_COORD_ARRAY)); else glCheck(glDisableClientState(GL_TEXTURE_COORD_ARRAY)); } // If we switch between non-cache and cache mode or enable texture // coordinates we need to set up the pointers to the vertices' components if (!m_cache.enable || !useVertexCache || !m_cache.useVertexCache) { const auto* data = reinterpret_cast(vertices); // If we pre-transform the vertices, we must use our internal vertex cache if (useVertexCache) data = reinterpret_cast(m_cache.vertexCache.data()); glCheck(glVertexPointer(2, GL_FLOAT, sizeof(Vertex), data + 0)); glCheck(glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(Vertex), data + 8)); if (enableTexCoordsArray) glCheck(glTexCoordPointer(2, GL_FLOAT, sizeof(Vertex), data + 12)); } else if (enableTexCoordsArray && !m_cache.texCoordsArrayEnabled) { // If we enter this block, we are already using our internal vertex cache const auto* data = reinterpret_cast(m_cache.vertexCache.data()); glCheck(glTexCoordPointer(2, GL_FLOAT, sizeof(Vertex), data + 12)); } drawPrimitives(type, 0, vertexCount); cleanupDraw(states); // Update the cache m_cache.useVertexCache = useVertexCache; m_cache.texCoordsArrayEnabled = enableTexCoordsArray; } } //////////////////////////////////////////////////////////// void RenderTarget::draw(const VertexBuffer& vertexBuffer, const RenderStates& states) { draw(vertexBuffer, 0, vertexBuffer.getVertexCount(), states); } //////////////////////////////////////////////////////////// void RenderTarget::draw(const VertexBuffer& vertexBuffer, std::size_t firstVertex, std::size_t vertexCount, const RenderStates& states) { // VertexBuffer not supported? if (!VertexBuffer::isAvailable()) { err() << "sf::VertexBuffer is not available, drawing skipped" << std::endl; return; } // Sanity check if (firstVertex > vertexBuffer.getVertexCount()) return; // Clamp vertexCount to something that makes sense vertexCount = std::min(vertexCount, vertexBuffer.getVertexCount() - firstVertex); // Nothing to draw? if (!vertexCount || !vertexBuffer.getNativeHandle()) return; if (RenderTargetImpl::isActive(m_id) || setActive(true)) { setupDraw(false, states); // Bind vertex buffer VertexBuffer::bind(&vertexBuffer); // Always enable texture coordinates if (!m_cache.enable || !m_cache.texCoordsArrayEnabled) glCheck(glEnableClientState(GL_TEXTURE_COORD_ARRAY)); glCheck(glVertexPointer(2, GL_FLOAT, sizeof(Vertex), reinterpret_cast(0))); glCheck(glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(Vertex), reinterpret_cast(8))); glCheck(glTexCoordPointer(2, GL_FLOAT, sizeof(Vertex), reinterpret_cast(12))); drawPrimitives(vertexBuffer.getPrimitiveType(), firstVertex, vertexCount); // Unbind vertex buffer VertexBuffer::bind(nullptr); cleanupDraw(states); // Update the cache m_cache.useVertexCache = false; m_cache.texCoordsArrayEnabled = true; } } //////////////////////////////////////////////////////////// bool RenderTarget::isSrgb() const { // By default sRGB encoding is not enabled for an arbitrary RenderTarget return false; } //////////////////////////////////////////////////////////// bool RenderTarget::setActive(bool active) { // Mark this RenderTarget as active or no longer active in the tracking map const std::lock_guard lock(RenderTargetImpl::getMutex()); const std::uint64_t contextId = Context::getActiveContextId(); using RenderTargetImpl::getContextRenderTargetMap; auto& contextRenderTargetMap = getContextRenderTargetMap(); const auto it = contextRenderTargetMap.find(contextId); if (active) { if (it == contextRenderTargetMap.end()) { contextRenderTargetMap[contextId] = m_id; m_cache.glStatesSet = false; m_cache.enable = false; } else if (it->second != m_id) { it->second = m_id; m_cache.enable = false; } } else { if (it != contextRenderTargetMap.end()) contextRenderTargetMap.erase(it); m_cache.enable = false; } return true; } //////////////////////////////////////////////////////////// void RenderTarget::pushGLStates() { if (RenderTargetImpl::isActive(m_id) || setActive(true)) { #ifdef SFML_DEBUG // make sure that the user didn't leave an unchecked OpenGL error const GLenum error = glGetError(); if (error != GL_NO_ERROR) { err() << "OpenGL error (" << error << ") detected in user code, " << "you should check for errors with glGetError()" << std::endl; } #endif #ifndef SFML_OPENGL_ES glCheck(glPushClientAttrib(GL_CLIENT_ALL_ATTRIB_BITS)); glCheck(glPushAttrib(GL_ALL_ATTRIB_BITS)); #endif glCheck(glMatrixMode(GL_MODELVIEW)); glCheck(glPushMatrix()); glCheck(glMatrixMode(GL_PROJECTION)); glCheck(glPushMatrix()); glCheck(glMatrixMode(GL_TEXTURE)); glCheck(glPushMatrix()); } resetGLStates(); } //////////////////////////////////////////////////////////// void RenderTarget::popGLStates() { if (RenderTargetImpl::isActive(m_id) || setActive(true)) { glCheck(glMatrixMode(GL_PROJECTION)); glCheck(glPopMatrix()); glCheck(glMatrixMode(GL_MODELVIEW)); glCheck(glPopMatrix()); glCheck(glMatrixMode(GL_TEXTURE)); glCheck(glPopMatrix()); #ifndef SFML_OPENGL_ES glCheck(glPopClientAttrib()); glCheck(glPopAttrib()); #endif } } //////////////////////////////////////////////////////////// void RenderTarget::resetGLStates() { // Check here to make sure a context change does not happen after activate(true) const bool shaderAvailable = Shader::isAvailable(); const bool vertexBufferAvailable = VertexBuffer::isAvailable(); // Workaround for states not being properly reset on // macOS unless a context switch really takes place #if defined(SFML_SYSTEM_MACOS) if (!setActive(false)) { err() << "Failed to set render target inactive" << std::endl; } #endif if (RenderTargetImpl::isActive(m_id) || setActive(true)) { // Make sure that extensions are initialized priv::ensureExtensionsInit(); // Make sure that the texture unit which is active is the number 0 if (GLEXT_multitexture) { glCheck(GLEXT_glClientActiveTexture(GLEXT_GL_TEXTURE0)); glCheck(GLEXT_glActiveTexture(GLEXT_GL_TEXTURE0)); } // 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)); glCheck(glEnable(GL_TEXTURE_2D)); glCheck(glEnable(GL_BLEND)); glCheck(glMatrixMode(GL_MODELVIEW)); glCheck(glLoadIdentity()); 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); if (vertexBufferAvailable) glCheck(VertexBuffer::bind(nullptr)); m_cache.texCoordsArrayEnabled = true; m_cache.useVertexCache = false; // Set the default view setView(getView()); m_cache.enable = true; } } //////////////////////////////////////////////////////////// void RenderTarget::initialize() { // Setup the default and current views m_defaultView = View(FloatRect({0, 0}, Vector2f(getSize()))); m_view = m_defaultView; // Set GL states only on first draw, so that we don't pollute user's states m_cache.glStatesSet = false; // Generate a unique ID for this RenderTarget to track // whether it is active within a specific context m_id = RenderTargetImpl::getUniqueId(); } //////////////////////////////////////////////////////////// void RenderTarget::applyCurrentView() { // Set the viewport const IntRect viewport = getViewport(m_view); const int viewportTop = static_cast(getSize().y) - (viewport.position.y + viewport.size.y); glCheck(glViewport(viewport.position.x, viewportTop, viewport.size.x, viewport.size.y)); // Set the scissor rectangle and enable/disable scissor testing if (m_view.getScissor() == FloatRect({0, 0}, {1, 1})) { if (!m_cache.enable || m_cache.scissorEnabled) { glCheck(glDisable(GL_SCISSOR_TEST)); m_cache.scissorEnabled = false; } } else { const IntRect pixelScissor = getScissor(m_view); const int scissorTop = static_cast(getSize().y) - (pixelScissor.position.y + pixelScissor.size.y); glCheck(glScissor(pixelScissor.position.x, scissorTop, pixelScissor.size.x, pixelScissor.size.y)); if (!m_cache.enable || !m_cache.scissorEnabled) { glCheck(glEnable(GL_SCISSOR_TEST)); m_cache.scissorEnabled = true; } } // Set the projection matrix glCheck(glMatrixMode(GL_PROJECTION)); glCheck(glLoadMatrixf(m_view.getTransform().getMatrix())); // Go back to model-view mode glCheck(glMatrixMode(GL_MODELVIEW)); m_cache.viewChanged = false; } //////////////////////////////////////////////////////////// void RenderTarget::applyBlendMode(const BlendMode& mode) { using RenderTargetImpl::equationToGlConstant; using RenderTargetImpl::factorToGlConstant; // Apply the blend mode, falling back to the non-separate versions if necessary if (GLEXT_blend_func_separate) { glCheck(GLEXT_glBlendFuncSeparate(factorToGlConstant(mode.colorSrcFactor), factorToGlConstant(mode.colorDstFactor), factorToGlConstant(mode.alphaSrcFactor), factorToGlConstant(mode.alphaDstFactor))); } else { glCheck(glBlendFunc(factorToGlConstant(mode.colorSrcFactor), factorToGlConstant(mode.colorDstFactor))); } if (GLEXT_blend_minmax || GLEXT_blend_subtract) { if (GLEXT_blend_equation_separate) { glCheck(GLEXT_glBlendEquationSeparate(equationToGlConstant(mode.colorEquation), equationToGlConstant(mode.alphaEquation))); } else { glCheck(GLEXT_glBlendEquation(equationToGlConstant(mode.colorEquation))); } } else if ((mode.colorEquation != BlendMode::Equation::Add) || (mode.alphaEquation != BlendMode::Equation::Add)) { static bool warned = false; if (!warned) { #ifdef SFML_OPENGL_ES err() << "OpenGL ES extension OES_blend_subtract unavailable" << std::endl; #else err() << "OpenGL extension EXT_blend_minmax and EXT_blend_subtract unavailable" << std::endl; #endif err() << "Selecting a blend equation not possible" << '\n' << "Ensure that hardware acceleration is enabled if available" << std::endl; warned = true; } } m_cache.lastBlendMode = 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.enable || 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.enable || !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) { // No need to call glMatrixMode(GL_MODELVIEW), it is always the // current mode (for optimization purpose, since it's the most used) if (transform == Transform::Identity) glCheck(glLoadIdentity()); else glCheck(glLoadMatrixf(transform.getMatrix())); } //////////////////////////////////////////////////////////// void RenderTarget::applyTexture(const Texture* texture, CoordinateType coordinateType) { Texture::bind(texture, coordinateType); m_cache.lastTextureId = texture ? texture->m_cacheId : 0; m_cache.lastCoordinateType = coordinateType; } //////////////////////////////////////////////////////////// void RenderTarget::applyShader(const Shader* shader) { Shader::bind(shader); } //////////////////////////////////////////////////////////// void RenderTarget::setupDraw(bool useVertexCache, const RenderStates& states) { // GL_FRAMEBUFFER_SRGB is not available on OpenGL ES // If a framebuffer supports sRGB, it will always be enabled on OpenGL ES #ifndef SFML_OPENGL_ES // Enable or disable sRGB encoding // This is needed for drivers that do not check the format of the surface drawn to before applying sRGB conversion if (!m_cache.enable) { if (isSrgb()) glCheck(glEnable(GL_FRAMEBUFFER_SRGB)); else glCheck(glDisable(GL_FRAMEBUFFER_SRGB)); } #endif // First set the persistent OpenGL states if it's the very first call if (!m_cache.glStatesSet) resetGLStates(); if (useVertexCache) { // Since vertices are transformed, we must use an identity transform to render them if (!m_cache.enable || !m_cache.useVertexCache) glCheck(glLoadIdentity()); } else { applyTransform(states.transform); } // Apply the view if (!m_cache.enable || m_cache.viewChanged) applyCurrentView(); // Apply the blend mode 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)) { // If the texture is an FBO attachment, always rebind it // in order to inform the OpenGL driver that we want changes // made to it in other contexts to be visible here as well // This saves us from having to call glFlush() in // RenderTextureImplFBO which can be quite costly // See: https://www.khronos.org/opengl/wiki/Memory_Model applyTexture(states.texture, states.coordinateType); } else { const std::uint64_t textureId = states.texture ? states.texture->m_cacheId : 0; if (textureId != m_cache.lastTextureId || states.coordinateType != m_cache.lastCoordinateType) applyTexture(states.texture, states.coordinateType); } // Apply the shader if (states.shader) applyShader(states.shader); } //////////////////////////////////////////////////////////// void RenderTarget::drawPrimitives(PrimitiveType type, std::size_t firstVertex, std::size_t vertexCount) { // Find the OpenGL primitive type static constexpr priv::EnumArray modes = {GL_POINTS, GL_LINES, GL_LINE_STRIP, GL_TRIANGLES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN}; const GLenum mode = modes[type]; // Draw the primitives glCheck(glDrawArrays(mode, static_cast(firstVertex), static_cast(vertexCount))); } //////////////////////////////////////////////////////////// void RenderTarget::cleanupDraw(const RenderStates& states) { // Unbind the shader, if any if (states.shader) applyShader(nullptr); // If the texture we used to draw belonged to a RenderTexture, then forcibly unbind that texture. // This prevents a bug where some drivers do not clear RenderTextures properly. 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; } } // namespace sf //////////////////////////////////////////////////////////// // Render states caching strategies // // * View // If SetView was called since last draw, the projection // matrix is updated. We don't need more, the view doesn't // change frequently. // // * Transform // The transform matrix is usually expensive because each // entity will most likely use a different transform. This can // lead, in worst case, to changing it every 4 vertices. // To avoid that, when the vertex count is low enough, we // pre-transform them and therefore use an identity transform // to render them. // // * Blending mode // Since it overloads the == operator, we can easily check // whether any of the 6 blending components changed and, // thus, whether we need to update the blend mode. // // * Texture // Storing the pointer or OpenGL ID of the last used texture // is not enough; if the sf::Texture instance is destroyed, // both the pointer and the OpenGL ID might be recycled in // a new texture instance. We need to use our own unique // identifier system to ensure consistent caching. // // * Shader // Shaders are very hard to optimize, because they have // parameters that can be hard (if not impossible) to track, // like matrices or textures. The only optimization that we // do is that we avoid setting a null shader if there was // already none for the previous draw. // ////////////////////////////////////////////////////////////