mirror of https://github.com/mosra/magnum.git
Browse Source
This is a replacement for the existing AbstractRenderer and Renderer2D / Renderer3D classes, with no STL or other shittiness like excessive allocations, and with a much better feature set. Deprecation of the old APIs is going to happen next. Compared to the old implementation it doesn't make use of the complicated buffer mapping because -- unlike in 2012 -- such behavior was since deemed questionable as the driver (or whichever translation layer like ANGLE or Zink or Apple's GL-over-Metal) may still make a copy anyway and doing so prevents buffer orphaning. So it's right now just plain setData() / setSubData() calls. *If* it becomes some sort of a bottleneck (which I doubt), I may reconsider, or add something else like double buffering.pull/674/head
12 changed files with 1437 additions and 8 deletions
@ -0,0 +1,306 @@ |
|||||||
|
/*
|
||||||
|
This file is part of Magnum. |
||||||
|
|
||||||
|
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, |
||||||
|
2020, 2021, 2022, 2023, 2024, 2025 |
||||||
|
Vladimír Vondruš <mosra@centrum.cz> |
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a |
||||||
|
copy of this software and associated documentation files (the "Software"), |
||||||
|
to deal in the Software without restriction, including without limitation |
||||||
|
the rights to use, copy, modify, merge, publish, distribute, sublicense, |
||||||
|
and/or sell copies of the Software, and to permit persons to whom the |
||||||
|
Software is furnished to do so, subject to the following conditions: |
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included |
||||||
|
in all copies or substantial portions of the Software. |
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
||||||
|
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
||||||
|
DEALINGS IN THE SOFTWARE. |
||||||
|
*/ |
||||||
|
|
||||||
|
#include "RendererGL.h" |
||||||
|
|
||||||
|
#include <Corrade/Containers/EnumSet.hpp> |
||||||
|
#include <Corrade/Containers/StringView.h> |
||||||
|
|
||||||
|
#include "Magnum/Shaders/GenericGL.h" /* no link-time dependency here */ |
||||||
|
#include "Magnum/Text/AbstractGlyphCache.h" |
||||||
|
#include "Magnum/Text/Implementation/rendererState.h" |
||||||
|
|
||||||
|
/* Somehow on GCC 4.8 to 7 the {} passed as a default argument for
|
||||||
|
ArrayView<const FeatureRange> causes "error: elements of array 'const class |
||||||
|
Magnum::Text::FeatureRange [0]' have incomplete type". GCC 9 is fine, no |
||||||
|
idea about version 8, but including the definition for it as well to be |
||||||
|
safe. Similar problem happens with MSVC STL, where the initializer_list is |
||||||
|
implemented as a (begin, end) pair and size() is a difference of those two |
||||||
|
pointers. Which needs to know the type size to calculate the actual element |
||||||
|
count. */ |
||||||
|
#if (defined(CORRADE_TARGET_GCC) && __GNUC__ <= 8) || defined(CORRADE_TARGET_DINKUMWARE) |
||||||
|
#include "Magnum/Text/Feature.h" |
||||||
|
#endif |
||||||
|
|
||||||
|
namespace Magnum { namespace Text { |
||||||
|
|
||||||
|
Debug& operator<<(Debug& debug, const RendererGLFlag value) { |
||||||
|
debug << "Text::RendererGLFlag" << Debug::nospace; |
||||||
|
|
||||||
|
switch(value) { |
||||||
|
/* LCOV_EXCL_START */ |
||||||
|
#define _c(v) case RendererGLFlag::v: return debug << "::" #v; |
||||||
|
_c(GlyphPositionsClusters) |
||||||
|
#undef _c |
||||||
|
/* LCOV_EXCL_STOP */ |
||||||
|
} |
||||||
|
|
||||||
|
return debug << "(" << Debug::nospace << Debug::hex << UnsignedByte(value) << Debug::nospace << ")"; |
||||||
|
} |
||||||
|
|
||||||
|
Debug& operator<<(Debug& debug, const RendererGLFlags value) { |
||||||
|
return Containers::enumSetDebugOutput(debug, value, "Text::RendererGLFlags{}", { |
||||||
|
RendererGLFlag::GlyphPositionsClusters |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
struct RendererGL::State: Renderer::State { |
||||||
|
explicit State(const AbstractGlyphCache& glyphCache, RendererGLFlags flags); |
||||||
|
|
||||||
|
GL::Buffer indices{GL::Buffer::TargetHint::ElementArray}, |
||||||
|
vertices{GL::Buffer::TargetHint::Array}; |
||||||
|
GL::Mesh mesh; |
||||||
|
|
||||||
|
/* Because querying GL buffer size is not possible on all platforms and it
|
||||||
|
may be slow, track the size here. It's used to know whether the buffer |
||||||
|
should be reuploaded as a whole or can be partially updated, updated in |
||||||
|
both reserve() and render(). */ |
||||||
|
UnsignedInt bufferGlyphCapacity = 0; |
||||||
|
}; |
||||||
|
|
||||||
|
RendererGL::State::State(const AbstractGlyphCache& glyphCache, RendererGLFlags flags): Renderer::State{glyphCache, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, RendererFlags{UnsignedByte(flags)}} { |
||||||
|
#ifdef MAGNUM_TARGET_GLES2 |
||||||
|
CORRADE_ASSERT(glyphCache.size().z() == 1, |
||||||
|
"Text::RendererGL: array glyph caches are not supported in OpenGL ES 2.0 and WebGL 1 builds", ); |
||||||
|
#endif |
||||||
|
|
||||||
|
/* As documented in RendererGL::setIndexType(), use of 8-bit indices is
|
||||||
|
discouraged on contemporary GPUs */ |
||||||
|
indexType = minIndexType = MeshIndexType::UnsignedShort; |
||||||
|
|
||||||
|
/* Set up the mesh with the initial index type and zero primitives to draw.
|
||||||
|
The count gets updated on each renderer(), index buffer properties each |
||||||
|
time the index type changes. */ |
||||||
|
mesh.setIndexBuffer(indices, 0, indexType) |
||||||
|
.setCount(0); |
||||||
|
#ifndef MAGNUM_TARGET_GLES2 |
||||||
|
if(glyphCache.size().z() != 1) { |
||||||
|
mesh.addVertexBuffer(vertices, 0, |
||||||
|
Shaders::GenericGL2D::Position{}, |
||||||
|
Shaders::GenericGL2D::TextureArrayCoordinates{}); |
||||||
|
} else |
||||||
|
#endif |
||||||
|
{ |
||||||
|
mesh.addVertexBuffer(vertices, 0, |
||||||
|
Shaders::GenericGL2D::Position{}, |
||||||
|
Shaders::GenericGL2D::TextureCoordinates{}); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
RendererGL::RendererGL(const AbstractGlyphCache& glyphCache, RendererGLFlags flags): Renderer{Containers::pointer<State>(glyphCache, flags)} {} |
||||||
|
|
||||||
|
RendererGL::RendererGL(RendererGL&&) noexcept = default; |
||||||
|
|
||||||
|
RendererGL::~RendererGL() = default; |
||||||
|
|
||||||
|
RendererGL& RendererGL::operator=(RendererGL&&) noexcept = default; |
||||||
|
|
||||||
|
RendererGLFlags RendererGL::flags() const { |
||||||
|
return RendererGLFlags{UnsignedByte(_state->flags)}; |
||||||
|
} |
||||||
|
|
||||||
|
GL::Mesh& RendererGL::mesh() { |
||||||
|
return static_cast<State&>(*_state).mesh; |
||||||
|
} |
||||||
|
|
||||||
|
const GL::Mesh& RendererGL::mesh() const { |
||||||
|
return static_cast<const State&>(*_state).mesh; |
||||||
|
} |
||||||
|
|
||||||
|
RendererGL& RendererGL::setIndexType(MeshIndexType atLeast) { |
||||||
|
State& state = static_cast<State&>(*_state); |
||||||
|
|
||||||
|
Renderer::setIndexType(atLeast); |
||||||
|
|
||||||
|
/* Upload indices anew if the type is different from before. In this case
|
||||||
|
it's also most likely that the size is bigger than before, so do it as |
||||||
|
a setData() call instead of having a specialized setSubData() code path |
||||||
|
if the total size shrinks. |
||||||
|
|
||||||
|
Besides the type, the capacity should not change compared to when the |
||||||
|
buffer was last updated in reserve() or render(). (Which only holds for |
||||||
|
builtin allocators, but RendererGL so far allows only builtin allocators |
||||||
|
so that's fine. It however does *not* hold for `state.indexData`, as |
||||||
|
that can stay larger if the index type becomes smaller, so verifying |
||||||
|
against `state.glyphPositions` instead.) */ |
||||||
|
CORRADE_INTERNAL_ASSERT(state.bufferGlyphCapacity == state.glyphPositions.size()); |
||||||
|
if(GL::meshIndexType(state.indexType) != state.mesh.indexType()) { |
||||||
|
state.indices.setData(state.indexData); |
||||||
|
state.mesh.setIndexBuffer(state.indices, 0, state.indexType); |
||||||
|
} |
||||||
|
|
||||||
|
return *this; |
||||||
|
} |
||||||
|
|
||||||
|
RendererGL& RendererGL::clear() { |
||||||
|
Renderer::clear(); |
||||||
|
static_cast<State&>(*_state).mesh.setCount(0); |
||||||
|
return *this; |
||||||
|
} |
||||||
|
|
||||||
|
RendererGL& RendererGL::reset() { |
||||||
|
Renderer::reset(); |
||||||
|
static_cast<State&>(*_state).mesh.setCount(0); |
||||||
|
return *this; |
||||||
|
} |
||||||
|
|
||||||
|
RendererGL& RendererGL::reserve(const UnsignedInt glyphCapacity, const UnsignedInt runCapacity) { |
||||||
|
State& state = static_cast<State&>(*_state); |
||||||
|
|
||||||
|
Renderer::reserve(glyphCapacity, runCapacity); |
||||||
|
|
||||||
|
/* Upload indices anew if the capacity is bigger than before */ |
||||||
|
if(state.bufferGlyphCapacity < glyphCapacity) { |
||||||
|
state.indices.setData(state.indexData); |
||||||
|
/* Update the mesh index buffer reference if the type changed */ |
||||||
|
if(GL::meshIndexType(state.indexType) != state.mesh.indexType()) |
||||||
|
state.mesh.setIndexBuffer(state.indices, 0, state.indexType); |
||||||
|
|
||||||
|
/* If the capacity isn't bigger, the index type shouldn't have changed
|
||||||
|
either and so no upload needs to be done. It can change only if the new |
||||||
|
capacity is too larger to fit the type used, or in a setIndexType() |
||||||
|
call, but there we handle the reupload directly. */ |
||||||
|
} else CORRADE_INTERNAL_ASSERT(GL::meshIndexType(state.indexType) == state.mesh.indexType()); |
||||||
|
|
||||||
|
/* Resize the vertex buffer and reupload its contents if the capacity is
|
||||||
|
bigger than before */ |
||||||
|
if(state.bufferGlyphCapacity < glyphCapacity) { |
||||||
|
const UnsignedInt glyphSize = 4*( |
||||||
|
#ifndef MAGNUM_TARGET_GLES2 |
||||||
|
state.glyphCache.size().z() != 1 ? |
||||||
|
sizeof(Implementation::VertexArray) : |
||||||
|
#endif |
||||||
|
sizeof(Implementation::Vertex)); |
||||||
|
|
||||||
|
/* The assumption in this case is that the capacity is bigger than the
|
||||||
|
actually rendered glyph count, otherwise we'd have it all resized |
||||||
|
and uploaded in render() already. Thus we have to do a bigger |
||||||
|
setData() allocation first and then upload just a portion with |
||||||
|
setSubData(). */ |
||||||
|
CORRADE_INTERNAL_ASSERT(glyphCapacity > state.glyphCount); |
||||||
|
state.vertices |
||||||
|
.setData({nullptr, glyphCapacity*glyphSize}) |
||||||
|
.setSubData(0, state.vertexData.prefix(state.glyphCount*glyphSize)); |
||||||
|
} |
||||||
|
|
||||||
|
/* Remember the currently used capacity if it grew. It can happen that
|
||||||
|
reserve() is called with a smaller capacity, or with just runCapacity |
||||||
|
being larger, so this shouldn't reset that and cause needless reupload |
||||||
|
next time. */ |
||||||
|
state.bufferGlyphCapacity = Math::max(state.bufferGlyphCapacity, glyphCapacity); |
||||||
|
|
||||||
|
return *this; |
||||||
|
} |
||||||
|
|
||||||
|
Containers::Pair<Range2D, Range1Dui> RendererGL::render() { |
||||||
|
State& state = static_cast<State&>(*_state); |
||||||
|
|
||||||
|
const Containers::Pair<Range2D, Range1Dui> out = Renderer::render(); |
||||||
|
|
||||||
|
/* Upload indices anew if the glyph count is bigger than before */ |
||||||
|
if(state.bufferGlyphCapacity < state.glyphCount) { |
||||||
|
state.indices.setData(state.indexData); |
||||||
|
/* Update the mesh index buffer reference if the type changed */ |
||||||
|
if(GL::meshIndexType(state.indexType) != state.mesh.indexType()) |
||||||
|
state.mesh.setIndexBuffer(state.indices, 0, state.indexType); |
||||||
|
|
||||||
|
/* If the glyph count isn't bigger, the index type shouldn't have changed
|
||||||
|
either. Same reasoning as in reserve() above. */ |
||||||
|
} else CORRADE_INTERNAL_ASSERT(GL::meshIndexType(state.indexType) == state.mesh.indexType()); |
||||||
|
|
||||||
|
/* Upload vertices fully anew if the glyph count is bigger than before */ |
||||||
|
const UnsignedInt glyphSize = 4*( |
||||||
|
#ifndef MAGNUM_TARGET_GLES2 |
||||||
|
state.glyphCache.size().z() != 1 ? |
||||||
|
sizeof(Implementation::VertexArray) : |
||||||
|
#endif |
||||||
|
sizeof(Implementation::Vertex)); |
||||||
|
if(state.bufferGlyphCapacity < state.glyphCount) { |
||||||
|
/* Unlike in render(), it's just setData() alone, with the assumption
|
||||||
|
that the render() caused the capacity to grow to fit exactly all |
||||||
|
glyphs, and so we upload everything. (Which only holds for builtin |
||||||
|
vertex allocators, but RendererGL so far allows only builtin |
||||||
|
allocators so that's fine.) */ |
||||||
|
CORRADE_INTERNAL_ASSERT( |
||||||
|
state.vertexPositions.size() == state.glyphCount*4 && |
||||||
|
state.vertexTextureCoordinates.size() == state.glyphCount*4); |
||||||
|
state.vertices.setData(state.vertexData.prefix(state.glyphCount*glyphSize)); |
||||||
|
|
||||||
|
/* Otherwise upload just what was rendered new */ |
||||||
|
} else { |
||||||
|
const Range1Dui glyphRange = glyphsForRuns(out.second()); |
||||||
|
state.vertices.setSubData(glyphRange.min()*glyphSize, state.vertexData.slice(glyphRange.min()*glyphSize, glyphRange.max()*glyphSize)); |
||||||
|
} |
||||||
|
|
||||||
|
/* Remember the currently used capacity if it grew */ |
||||||
|
state.bufferGlyphCapacity = Math::max(state.bufferGlyphCapacity, state.glyphCount); |
||||||
|
|
||||||
|
/* Set the mesh index count to exactly what was rendered in total */ |
||||||
|
state.mesh.setCount(state.glyphCount*6); |
||||||
|
|
||||||
|
return out; |
||||||
|
} |
||||||
|
|
||||||
|
RendererGL& RendererGL::add(AbstractShaper& shaper, const Float size, const Containers::StringView text, const UnsignedInt begin, const UnsignedInt end, const Containers::ArrayView<const FeatureRange> features) { |
||||||
|
return static_cast<RendererGL&>(Renderer::add(shaper, size, text, begin, end, features)); |
||||||
|
} |
||||||
|
|
||||||
|
RendererGL& RendererGL::add(AbstractShaper& shaper, const Float size, const Containers::StringView text, const UnsignedInt begin, const UnsignedInt end) { |
||||||
|
return static_cast<RendererGL&>(Renderer::add(shaper, size, text, begin, end)); |
||||||
|
} |
||||||
|
|
||||||
|
RendererGL& RendererGL::add(AbstractShaper& shaper, const Float size, const Containers::StringView text, const UnsignedInt begin, const UnsignedInt end, const std::initializer_list<FeatureRange> features) { |
||||||
|
return static_cast<RendererGL&>(Renderer::add(shaper, size, text, begin, end, features)); |
||||||
|
} |
||||||
|
|
||||||
|
RendererGL& RendererGL::add(AbstractShaper& shaper, const Float size, const Containers::StringView text, const Containers::ArrayView<const FeatureRange> features) { |
||||||
|
return static_cast<RendererGL&>(Renderer::add(shaper, size, text, features)); |
||||||
|
} |
||||||
|
|
||||||
|
RendererGL& RendererGL::add(AbstractShaper& shaper, const Float size, const Containers::StringView text) { |
||||||
|
return static_cast<RendererGL&>(Renderer::add(shaper, size, text)); |
||||||
|
} |
||||||
|
|
||||||
|
RendererGL& RendererGL::add(AbstractShaper& shaper, const Float size, const Containers::StringView text, const std::initializer_list<FeatureRange> features) { |
||||||
|
return static_cast<RendererGL&>(Renderer::add(shaper, size, text, features)); |
||||||
|
} |
||||||
|
|
||||||
|
Containers::Pair<Range2D, Range1Dui> RendererGL::render(AbstractShaper& shaper, const Float size, const Containers::StringView text, const Containers::ArrayView<const FeatureRange> features) { |
||||||
|
/* Compared to Renderer::render() this calls our render() instead of
|
||||||
|
Renderer::render() */ |
||||||
|
add(shaper, size, text, features); |
||||||
|
return render(); |
||||||
|
} |
||||||
|
|
||||||
|
Containers::Pair<Range2D, Range1Dui> RendererGL::render(AbstractShaper& shaper, const Float size, const Containers::StringView text) { |
||||||
|
return render(shaper, size, text, {}); |
||||||
|
} |
||||||
|
|
||||||
|
Containers::Pair<Range2D, Range1Dui> RendererGL::render(AbstractShaper& shaper, const Float size, const Containers::StringView text, const std::initializer_list<FeatureRange> features) { |
||||||
|
return render(shaper, size, text, Containers::arrayView(features)); |
||||||
|
} |
||||||
|
|
||||||
|
}} |
||||||
@ -0,0 +1,245 @@ |
|||||||
|
#ifndef Magnum_Text_RendererGL_h |
||||||
|
#define Magnum_Text_RendererGL_h |
||||||
|
/*
|
||||||
|
This file is part of Magnum. |
||||||
|
|
||||||
|
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, |
||||||
|
2020, 2021, 2022, 2023, 2024, 2025 |
||||||
|
Vladimír Vondruš <mosra@centrum.cz> |
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a |
||||||
|
copy of this software and associated documentation files (the "Software"), |
||||||
|
to deal in the Software without restriction, including without limitation |
||||||
|
the rights to use, copy, modify, merge, publish, distribute, sublicense, |
||||||
|
and/or sell copies of the Software, and to permit persons to whom the |
||||||
|
Software is furnished to do so, subject to the following conditions: |
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included |
||||||
|
in all copies or substantial portions of the Software. |
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
||||||
|
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
||||||
|
DEALINGS IN THE SOFTWARE. |
||||||
|
*/ |
||||||
|
|
||||||
|
/** @file
|
||||||
|
* @brief Class @ref Magnum::Text::RendererGL, enum @ref Magnum::Text::RendererGLFlag, enum set @ref Magnum::Text::RendererGLFlags |
||||||
|
* @m_since_latest |
||||||
|
*/ |
||||||
|
|
||||||
|
#include "Magnum/configure.h" |
||||||
|
|
||||||
|
#ifdef MAGNUM_TARGET_GL |
||||||
|
#include "Magnum/Text/Renderer.h" |
||||||
|
|
||||||
|
namespace Magnum { namespace Text { |
||||||
|
|
||||||
|
/**
|
||||||
|
@brief OpenGL text renderer flag |
||||||
|
@m_since_latest |
||||||
|
|
||||||
|
A superset of @ref RendererFlag. |
||||||
|
|
||||||
|
@note This enum is available only if Magnum is compiled with |
||||||
|
@ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features |
||||||
|
for more information. |
||||||
|
|
||||||
|
@see @ref RendererGLFlags, @ref RendererGL |
||||||
|
*/ |
||||||
|
/* Currently is the same as RendererFlag, but is made to a dedicated type to
|
||||||
|
not cause a breaking change once GL-specific flags are introduced, such as |
||||||
|
buffer mapping */ |
||||||
|
enum class RendererGLFlag: UnsignedByte { |
||||||
|
/** @copydoc RendererFlag::GlyphPositionsClusters */ |
||||||
|
GlyphPositionsClusters = UnsignedByte(RendererFlag::GlyphPositionsClusters) |
||||||
|
}; |
||||||
|
|
||||||
|
/**
|
||||||
|
@debugoperatorenum{RendererGLFlag} |
||||||
|
@m_since_latest |
||||||
|
|
||||||
|
@note This function is available only if Magnum is compiled with |
||||||
|
@ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features |
||||||
|
for more information. |
||||||
|
*/ |
||||||
|
MAGNUM_TEXT_EXPORT Debug& operator<<(Debug& output, RendererGLFlag value); |
||||||
|
|
||||||
|
/**
|
||||||
|
@brief OpenGL text renderer flags |
||||||
|
@m_since_latest |
||||||
|
|
||||||
|
@note This enum set is available only if Magnum is compiled with |
||||||
|
@ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features |
||||||
|
for more information. |
||||||
|
|
||||||
|
@see @ref RendererGL |
||||||
|
*/ |
||||||
|
typedef Containers::EnumSet<RendererGLFlag> RendererGLFlags; |
||||||
|
|
||||||
|
CORRADE_ENUMSET_OPERATORS(RendererGLFlags) |
||||||
|
|
||||||
|
/**
|
||||||
|
@debugoperatorenum{RendererGLFlags} |
||||||
|
@m_since_latest |
||||||
|
|
||||||
|
@note This function is available only if Magnum is compiled with |
||||||
|
@ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features |
||||||
|
for more information. |
||||||
|
*/ |
||||||
|
MAGNUM_TEXT_EXPORT Debug& operator<<(Debug& output, RendererGLFlags value); |
||||||
|
|
||||||
|
/**
|
||||||
|
@brief OpenGL text renderer |
||||||
|
@m_since_latest |
||||||
|
|
||||||
|
@note This class is available only if Magnum is compiled with |
||||||
|
@ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features |
||||||
|
for more information. |
||||||
|
*/ |
||||||
|
class MAGNUM_TEXT_EXPORT RendererGL: public Renderer { |
||||||
|
public: |
||||||
|
/**
|
||||||
|
* @brief Construct |
||||||
|
* @param glyphCache Glyph cache to use |
||||||
|
* @param flags Opt-in feature flags |
||||||
|
* |
||||||
|
* Unlike with the @ref Renderer base, the OpenGL implementation needs |
||||||
|
* to have a complete control over memory layout and allocation and |
||||||
|
* thus it isn't possible to supply custom allocators. If you want the |
||||||
|
* control, use @ref Renderer with custom index and vertex allocators |
||||||
|
* and fill a @ref GL::Mesh instance with the data manually. |
||||||
|
*/ |
||||||
|
explicit RendererGL(const AbstractGlyphCache& glyphCache, RendererGLFlags flags = {}); |
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Construct without creating the internal state and the OpenGL objects |
||||||
|
* |
||||||
|
* The constructed instance is equivalent to moved-from state, i.e. no |
||||||
|
* APIs can be safely called on the object. Useful in cases where you |
||||||
|
* will overwrite the instance later anyway. Move another object over |
||||||
|
* it to make it useful. |
||||||
|
* |
||||||
|
* This function can be safely used for constructing (and later |
||||||
|
* destructing) objects even without any OpenGL context being active. |
||||||
|
* However note that this is a low-level and a potentially dangerous |
||||||
|
* API, see the documentation of @ref NoCreate for alternatives. |
||||||
|
*/ |
||||||
|
explicit RendererGL(NoCreateT) noexcept: Renderer{NoCreate} {} |
||||||
|
|
||||||
|
/** @brief Copying is not allowed */ |
||||||
|
RendererGL(RendererGL&) = delete; |
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Move constructor |
||||||
|
* |
||||||
|
* Performs a destructive move, i.e. the original object isn't usable |
||||||
|
* afterwards anymore. |
||||||
|
*/ |
||||||
|
RendererGL(RendererGL&&) noexcept; |
||||||
|
|
||||||
|
~RendererGL(); |
||||||
|
|
||||||
|
/** @brief Copying is not allowed */ |
||||||
|
RendererGL& operator=(RendererGL&) = delete; |
||||||
|
|
||||||
|
/** @brief Move assignment */ |
||||||
|
RendererGL& operator=(RendererGL&&) noexcept; |
||||||
|
|
||||||
|
/** @brief Flags */ |
||||||
|
RendererGLFlags flags() const; |
||||||
|
|
||||||
|
/** @brief Mesh containing the rendered index and vertex data */ |
||||||
|
GL::Mesh& mesh(); |
||||||
|
const GL::Mesh& mesh() const; /**< @overload */ |
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set index type |
||||||
|
* @return Reference to self (for method chaining) |
||||||
|
* |
||||||
|
* Calls @ref Renderer::setIndexType() and updates @ref mesh() with the |
||||||
|
* rendered index data, if different from before. Compared to |
||||||
|
* @ref Renderer, the default index type is |
||||||
|
* @ref MeshIndexType::UnsignedShort, not |
||||||
|
* @ref MeshIndexType::UnsignedByte, as use of 8-bit indices is |
||||||
|
* discouraged on contemporary GPUs. |
||||||
|
*/ |
||||||
|
RendererGL& setIndexType(MeshIndexType atLeast); |
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reserve capacity for given glyph count |
||||||
|
* @return Reference to self (for method chaining) |
||||||
|
* |
||||||
|
* Calls @ref Renderer::reserve() and updates @ref mesh() with the |
||||||
|
* rendered index data, if different from before. |
||||||
|
*/ |
||||||
|
RendererGL& reserve(UnsignedInt glyphCapacity, UnsignedInt runCapacity); |
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Clear rendered glyphs, runs and vertices |
||||||
|
* @return Reference to self (for method chaining) |
||||||
|
* |
||||||
|
* Calls @ref Renderer::clear() and additionally also sets @ref mesh() |
||||||
|
* index count to @cpp 0 @ce. |
||||||
|
*/ |
||||||
|
RendererGL& clear(); |
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reset internal renderer state |
||||||
|
* @return Reference to self (for method chaining) |
||||||
|
* |
||||||
|
* Calls @ref Renderer::reset(), and additionally also sets @ref mesh() |
||||||
|
* index count to @cpp 0 @ce. |
||||||
|
*/ |
||||||
|
RendererGL& reset(); |
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Wrap up rendering of all text added so far |
||||||
|
* |
||||||
|
* Calls @ref Renderer::render(), updates @ref mesh() with the newly |
||||||
|
* rendered vertex data and potentially updates also the index data, if |
||||||
|
* different from before. |
||||||
|
*/ |
||||||
|
Containers::Pair<Range2D, Range1Dui> render(); |
||||||
|
|
||||||
|
/* Overloads to remove a WTF factor from method chaining order, and to
|
||||||
|
ensure our render() is called instead of Render::render() */ |
||||||
|
#ifndef DOXYGEN_GENERATING_OUTPUT |
||||||
|
RendererGL& setCursor(const Vector2& cursor) { |
||||||
|
return static_cast<RendererGL&>(Renderer::setCursor(cursor)); |
||||||
|
} |
||||||
|
RendererGL& setAlignment(Alignment alignment) { |
||||||
|
return static_cast<RendererGL&>(Renderer::setAlignment(alignment)); |
||||||
|
} |
||||||
|
RendererGL& setLineAdvance(Float advance) { |
||||||
|
return static_cast<RendererGL&>(Renderer::setLineAdvance(advance)); |
||||||
|
} |
||||||
|
RendererGL& setLayoutDirection(LayoutDirection direction) { |
||||||
|
return static_cast<RendererGL&>(Renderer::setLayoutDirection(direction)); |
||||||
|
} |
||||||
|
|
||||||
|
RendererGL& add(AbstractShaper& shaper, Float size, Containers::StringView text, UnsignedInt begin, UnsignedInt end, Containers::ArrayView<const FeatureRange> features); |
||||||
|
RendererGL& add(AbstractShaper& shaper, Float size, Containers::StringView text, UnsignedInt begin, UnsignedInt end); |
||||||
|
RendererGL& add(AbstractShaper& shaper, Float size, Containers::StringView text, UnsignedInt begin, UnsignedInt end, std::initializer_list<FeatureRange> features); |
||||||
|
RendererGL& add(AbstractShaper& shaper, Float size, Containers::StringView text, Containers::ArrayView<const FeatureRange> features); |
||||||
|
RendererGL& add(AbstractShaper& shaper, Float size, Containers::StringView text); |
||||||
|
RendererGL& add(AbstractShaper& shaper, Float size, Containers::StringView text, std::initializer_list<FeatureRange> features); |
||||||
|
|
||||||
|
Containers::Pair<Range2D, Range1Dui> render(AbstractShaper& shaper, Float size, Containers::StringView text, Containers::ArrayView<const FeatureRange> features); |
||||||
|
Containers::Pair<Range2D, Range1Dui> render(AbstractShaper& shaper, Float size, Containers::StringView text); |
||||||
|
Containers::Pair<Range2D, Range1Dui> render(AbstractShaper& shaper, Float size, Containers::StringView text, std::initializer_list<FeatureRange> features); |
||||||
|
#endif |
||||||
|
|
||||||
|
private: |
||||||
|
struct State; |
||||||
|
}; |
||||||
|
|
||||||
|
}} |
||||||
|
#else |
||||||
|
#error this header is available only in the OpenGL build |
||||||
|
#endif |
||||||
|
|
||||||
|
#endif |
||||||
@ -0,0 +1,74 @@ |
|||||||
|
/*
|
||||||
|
This file is part of Magnum. |
||||||
|
|
||||||
|
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, |
||||||
|
2020, 2021, 2022, 2023, 2024, 2025 |
||||||
|
Vladimír Vondruš <mosra@centrum.cz> |
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a |
||||||
|
copy of this software and associated documentation files (the "Software"), |
||||||
|
to deal in the Software without restriction, including without limitation |
||||||
|
the rights to use, copy, modify, merge, publish, distribute, sublicense, |
||||||
|
and/or sell copies of the Software, and to permit persons to whom the |
||||||
|
Software is furnished to do so, subject to the following conditions: |
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included |
||||||
|
in all copies or substantial portions of the Software. |
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
||||||
|
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
||||||
|
DEALINGS IN THE SOFTWARE. |
||||||
|
*/ |
||||||
|
|
||||||
|
#include <Corrade/Containers/String.h> |
||||||
|
#include <Corrade/TestSuite/Tester.h> |
||||||
|
|
||||||
|
#include "Magnum/Text/RendererGL.h" |
||||||
|
|
||||||
|
namespace Magnum { namespace Text { namespace Test { namespace { |
||||||
|
|
||||||
|
struct RendererGL_Test: TestSuite::Tester { |
||||||
|
explicit RendererGL_Test(); |
||||||
|
|
||||||
|
void debugFlag(); |
||||||
|
void debugFlags(); |
||||||
|
|
||||||
|
void constructNoCreate(); |
||||||
|
}; |
||||||
|
|
||||||
|
RendererGL_Test::RendererGL_Test() { |
||||||
|
addTests({&RendererGL_Test::debugFlag, |
||||||
|
&RendererGL_Test::debugFlags, |
||||||
|
|
||||||
|
&RendererGL_Test::constructNoCreate}); |
||||||
|
} |
||||||
|
|
||||||
|
void RendererGL_Test::debugFlag() { |
||||||
|
Containers::String out; |
||||||
|
Debug{&out} << RendererGLFlag::GlyphPositionsClusters << RendererGLFlag(0xca); |
||||||
|
CORRADE_COMPARE(out, "Text::RendererGLFlag::GlyphPositionsClusters Text::RendererGLFlag(0xca)\n"); |
||||||
|
} |
||||||
|
|
||||||
|
void RendererGL_Test::debugFlags() { |
||||||
|
Containers::String out; |
||||||
|
Debug{&out} << (RendererGLFlag::GlyphPositionsClusters|RendererGLFlag(0xf0)) << RendererGLFlags{}; |
||||||
|
CORRADE_COMPARE(out, "Text::RendererGLFlag::GlyphPositionsClusters|Text::RendererGLFlag(0xf0) Text::RendererGLFlags{}\n"); |
||||||
|
} |
||||||
|
|
||||||
|
void RendererGL_Test::constructNoCreate() { |
||||||
|
RendererGL renderer{NoCreate}; |
||||||
|
|
||||||
|
/* Shouldn't crash */ |
||||||
|
CORRADE_VERIFY(true); |
||||||
|
|
||||||
|
/* Implicit construction is not allowed */ |
||||||
|
CORRADE_VERIFY(!std::is_convertible<NoCreateT, RendererGL>::value); |
||||||
|
} |
||||||
|
|
||||||
|
}}}} |
||||||
|
|
||||||
|
CORRADE_TEST_MAIN(Magnum::Text::Test::RendererGL_Test) |
||||||
Binary file not shown.
Binary file not shown.
Loading…
Reference in new issue