Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-22 13:58:55 +01:00
parent 4af19165ec
commit 68073add76
12458 changed files with 12350765 additions and 2 deletions

199
libs/drape/CMakeLists.txt Normal file
View file

@ -0,0 +1,199 @@
project(drape)
set(DRAPE_TESTABLE_SRC
attribute_buffer_mutator.cpp
attribute_buffer_mutator.hpp
attribute_provider.cpp
attribute_provider.hpp
batcher.cpp
batcher.hpp
batcher_helpers.cpp
batcher_helpers.hpp
binding_info.cpp
binding_info.hpp
buffer_base.cpp
buffer_base.hpp
color.hpp
constants.hpp
cpu_buffer.cpp
cpu_buffer.hpp
data_buffer.cpp
data_buffer.hpp
data_buffer_impl.hpp
debug_renderer.hpp
drape_diagnostics.hpp
drape_global.hpp
drape_routine.hpp
dynamic_texture.hpp
font_constants.hpp
font_texture.cpp
font_texture.hpp
framebuffer.cpp
framebuffer.hpp
gl_constants.cpp
gl_constants.hpp
gl_gpu_program.cpp
gl_gpu_program.hpp
gl_extensions_list.cpp
gl_extensions_list.hpp
gl_functions.hpp
gl_includes.hpp
glsl_func.hpp
glsl_types.hpp
glyph.hpp
glyph_manager.cpp
glyph_manager.hpp
gpu_buffer.cpp
gpu_buffer.hpp
gpu_program.hpp
graphics_context.hpp
graphics_context_factory.cpp
graphics_context_factory.hpp
hw_texture.cpp
hw_texture.hpp
index_buffer.cpp
index_buffer.hpp
index_buffer_mutator.cpp
index_buffer_mutator.hpp
index_storage.cpp
index_storage.hpp
mesh_object.cpp
mesh_object.hpp
object_pool.hpp
oglcontext.cpp
oglcontext.hpp
overlay_handle.cpp
overlay_handle.hpp
overlay_tree.cpp
overlay_tree.hpp
pointers.cpp
pointers.hpp
render_bucket.cpp
render_bucket.hpp
render_state.cpp
render_state.hpp
shader.cpp
shader.hpp
static_texture.cpp
static_texture.hpp
stipple_pen_resource.cpp
stipple_pen_resource.hpp
support_manager.cpp
support_manager.hpp
symbols_texture.cpp
symbols_texture.hpp
harfbuzz_shaping.cpp
harfbuzz_shaping.hpp
texture.cpp
texture.hpp
texture_manager.cpp
texture_manager.hpp
texture_of_colors.cpp
texture_of_colors.hpp
texture_types.hpp
tm_read_resources.hpp
uniform_value.cpp
uniform_value.hpp
utils/glyph_usage_tracker.cpp
utils/glyph_usage_tracker.hpp
utils/gpu_mem_tracker.cpp
utils/gpu_mem_tracker.hpp
utils/projection.cpp
utils/projection.hpp
utils/vertex_decl.cpp
utils/vertex_decl.hpp
vertex_array_buffer.cpp
vertex_array_buffer.hpp
viewport.cpp
viewport.hpp
visual_scale.hpp
vulkan/vulkan_base_context.cpp
vulkan/vulkan_base_context.hpp
vulkan/vulkan_context_factory.cpp
vulkan/vulkan_context_factory.hpp
vulkan/vulkan_gpu_buffer_impl.cpp
vulkan/vulkan_gpu_buffer_impl.hpp
vulkan/vulkan_gpu_program.hpp
vulkan/vulkan_layers.cpp
vulkan/vulkan_layers.hpp
vulkan/vulkan_memory_manager.cpp
vulkan/vulkan_memory_manager.hpp
vulkan/vulkan_mesh_object_impl.cpp
vulkan/vulkan_object_manager.cpp
vulkan/vulkan_object_manager.hpp
vulkan/vulkan_param_descriptor.cpp
vulkan/vulkan_param_descriptor.hpp
vulkan/vulkan_pipeline.cpp
vulkan/vulkan_pipeline.hpp
vulkan/vulkan_staging_buffer.cpp
vulkan/vulkan_staging_buffer.hpp
vulkan/vulkan_texture.cpp
vulkan/vulkan_texture.hpp
vulkan/vulkan_utils.cpp
vulkan/vulkan_utils.hpp
vulkan/vulkan_vertex_array_buffer_impl.cpp
)
if (PLATFORM_IPHONE OR PLATFORM_MAC)
append(DRAPE_TESTABLE_SRC
metal/metal_base_context.hpp
metal/metal_base_context.mm
metal/metal_cleaner.hpp
metal/metal_cleaner.mm
metal/metal_gpu_buffer_impl.mm
metal/metal_gpu_program.hpp
metal/metal_mesh_object_impl.mm
metal/metal_states.hpp
metal/metal_states.mm
metal/metal_texture.hpp
metal/metal_texture.mm
metal/metal_vertex_array_buffer_impl.mm
metal/render_state_metal.mm
)
endif()
set(SRC ${DRAPE_TESTABLE_SRC} gl_functions.cpp)
if (PLATFORM_IPHONE)
append(SRC
hw_texture_ios.hpp
hw_texture_ios.mm
)
endif()
omim_add_library(${PROJECT_NAME} ${SRC})
# Do not include glm's CMakeLists.txt, because it's outdated and not necessary.
target_include_directories(${PROJECT_NAME} PUBLIC ${OMIM_ROOT}/3party/glm)
if (PLATFORM_LINUX OR PLATFORM_WIN)
find_package(OpenGL)
endif()
if (PLATFORM_MAC)
set_target_properties(${PROJECT_NAME} PROPERTIES XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC YES)
endif()
set(DRAPE_LINK_LIBRARIES
indexer
platform
geometry
coding
base
Freetype::Freetype
vulkan_wrapper
stb_image
harfbuzz::harfbuzz
ICU::i18n
expat::expat
$<$<BOOL:${PLATFORM_MAC}>:-framework\ OpenGL>
$<$<BOOL:${PLATFORM_MAC}>:-framework\ Metal>
$<$<BOOL:${PLATFORM_LINUX}>:OpenGL::GL>
$<$<BOOL:${PLATFORM_WIN}>:OpenGL::GL>
)
target_link_libraries(${PROJECT_NAME} ${DRAPE_LINK_LIBRARIES})
omim_add_test_subdirectory(drape_tests)
omim_add_tool_subdirectory(fonts_tool)

View file

@ -0,0 +1,25 @@
#include "drape/attribute_buffer_mutator.hpp"
namespace dp
{
AttributeBufferMutator::~AttributeBufferMutator()
{
SharedBufferManager & mng = SharedBufferManager::instance();
for (size_t i = 0; i < m_array.size(); ++i)
{
TBufferNode & node = m_array[i];
mng.freeSharedBuffer(node.second, node.first);
}
}
void AttributeBufferMutator::AddMutation(BindingInfo const & info, MutateNode const & node)
{
m_data[info].push_back(node);
}
void * AttributeBufferMutator::AllocateMutationBuffer(size_t byteCount)
{
m_array.push_back(make_pair(SharedBufferManager::instance().reserveSharedBuffer(byteCount), byteCount));
return &((*m_array.back().first)[0]);
}
} // namespace dp

View file

@ -0,0 +1,49 @@
#pragma once
#include "drape/binding_info.hpp"
#include "drape/pointers.hpp"
#include "base/shared_buffer_manager.hpp"
#include <cstdint>
#include <map>
#include <utility>
#include <vector>
namespace dp
{
struct MutateRegion
{
MutateRegion() : m_offset(0), m_count(0) {}
MutateRegion(uint32_t offset, uint32_t count) : m_offset(offset), m_count(count) {}
uint32_t m_offset; // Offset from buffer begin in "Elements" not in bytes.
uint32_t m_count; // Count of "Elements".
};
struct MutateNode
{
MutateRegion m_region;
ref_ptr<void> m_data;
};
class AttributeBufferMutator
{
using TBufferNode = std::pair<SharedBufferManager::shared_buffer_ptr_t, size_t>;
using TBufferArray = std::vector<TBufferNode>;
using TMutateNodes = std::vector<MutateNode>;
using TMutateData = std::map<BindingInfo, TMutateNodes>;
public:
~AttributeBufferMutator();
void AddMutation(BindingInfo const & info, MutateNode const & node);
void * AllocateMutationBuffer(size_t byteCount);
private:
friend class VertexArrayBuffer;
TMutateData const & GetMutateData() const { return m_data; }
TMutateData m_data;
TBufferArray m_array;
};
} // namespace dp

View file

@ -0,0 +1,107 @@
#include "drape/attribute_provider.hpp"
#include "base/assert.hpp"
#include "base/stl_helpers.hpp"
#ifdef DEBUG
#define INIT_CHECK_INFO(x) m_checkInfo = std::vector<bool>((std::vector<bool>::size_type)(x), false);
#define CHECK_STREAMS CheckStreams()
#define INIT_STREAM(x) InitCheckStream((x))
#else
#include "base/macros.hpp"
#define INIT_CHECK_INFO(x) UNUSED_VALUE((x))
#define CHECK_STREAMS
#define INIT_STREAM(x) UNUSED_VALUE((x))
#endif
namespace dp
{
AttributeProvider::AttributeProvider(uint8_t streamCount, uint32_t vertexCount) : m_vertexCount(vertexCount)
{
m_streams.resize(streamCount);
INIT_CHECK_INFO(streamCount);
}
bool AttributeProvider::IsDataExists() const
{
CHECK_STREAMS;
return m_vertexCount > 0;
}
uint32_t AttributeProvider::GetVertexCount() const
{
return m_vertexCount;
}
uint8_t AttributeProvider::GetStreamCount() const
{
return m_streams.size();
}
void const * AttributeProvider::GetRawPointer(uint8_t streamIndex)
{
ASSERT_LESS(streamIndex, GetStreamCount(), ());
CHECK_STREAMS;
return m_streams[streamIndex].m_data.get();
}
BindingInfo const & AttributeProvider::GetBindingInfo(uint8_t streamIndex) const
{
ASSERT_LESS(streamIndex, GetStreamCount(), ());
CHECK_STREAMS;
return m_streams[streamIndex].m_binding;
}
void AttributeProvider::Advance(uint32_t vertexCount)
{
ASSERT_LESS_OR_EQUAL(vertexCount, m_vertexCount, ());
CHECK_STREAMS;
if (m_vertexCount != vertexCount)
{
for (size_t i = 0; i < GetStreamCount(); ++i)
{
BindingInfo const & info = m_streams[i].m_binding;
uint32_t offset = vertexCount * info.GetElementSize();
void * rawPointer = m_streams[i].m_data.get();
m_streams[i].m_data = make_ref((void *)(((uint8_t *)rawPointer) + offset));
}
}
m_vertexCount -= vertexCount;
}
void AttributeProvider::InitStream(uint8_t streamIndex, BindingInfo const & bindingInfo, ref_ptr<void> data)
{
ASSERT_LESS(streamIndex, GetStreamCount(), ());
AttributeStream s;
s.m_binding = bindingInfo;
s.m_data = data;
m_streams[streamIndex] = s;
INIT_STREAM(streamIndex);
}
void AttributeProvider::Reset(uint32_t vertexCount)
{
m_vertexCount = vertexCount;
}
void AttributeProvider::UpdateStream(uint8_t streamIndex, ref_ptr<void> data)
{
ASSERT_LESS(streamIndex, GetStreamCount(), ());
m_streams[streamIndex].m_data = data;
INIT_STREAM(streamIndex);
}
#ifdef DEBUG
void AttributeProvider::CheckStreams() const
{
ASSERT(!base::IsExist(m_checkInfo, false), ("Not all streams initialized"));
}
void AttributeProvider::InitCheckStream(uint8_t streamIndex)
{
m_checkInfo[streamIndex] = true;
}
#endif
} // namespace dp

View file

@ -0,0 +1,44 @@
#pragma once
#include "drape/binding_info.hpp"
#include "drape/pointers.hpp"
#include <vector>
namespace dp
{
class AttributeProvider
{
public:
AttributeProvider(uint8_t streamCount, uint32_t vertexCount);
bool IsDataExists() const;
uint32_t GetVertexCount() const;
uint8_t GetStreamCount() const;
void const * GetRawPointer(uint8_t streamIndex);
BindingInfo const & GetBindingInfo(uint8_t streamIndex) const;
void Advance(uint32_t vertexCount);
void InitStream(uint8_t streamIndex, BindingInfo const & bindingInfo, ref_ptr<void> data);
void Reset(uint32_t vertexCount);
void UpdateStream(uint8_t streamIndex, ref_ptr<void> data);
private:
uint32_t m_vertexCount;
struct AttributeStream
{
BindingInfo m_binding;
ref_ptr<void> m_data;
};
std::vector<AttributeStream> m_streams;
#ifdef DEBUG
void CheckStreams() const;
void InitCheckStream(uint8_t streamIndex);
std::vector<bool> m_checkInfo;
#endif
};
} // namespace dp

299
libs/drape/batcher.cpp Normal file
View file

@ -0,0 +1,299 @@
#include "drape/batcher.hpp"
#include "drape/batcher_helpers.hpp"
#include "drape/cpu_buffer.hpp"
#include "drape/index_storage.hpp"
#include "drape/vertex_array_buffer.hpp"
#include "base/assert.hpp"
#include "base/stl_helpers.hpp"
#include <utility>
namespace dp
{
class Batcher::CallbacksWrapper : public BatchCallbacks
{
public:
CallbacksWrapper(RenderState const & state, ref_ptr<OverlayHandle> overlay, ref_ptr<Batcher> batcher)
: m_state(state)
, m_overlay(overlay)
, m_batcher(batcher)
{}
void SetVAO(ref_ptr<VertexArrayBuffer> buffer)
{
// Invocation with non-null VAO will cause to invalid range of indices.
// It means that VAO has been changed during batching.
if (m_buffer != nullptr)
m_vaoChanged = true;
m_buffer = buffer;
m_indicesRange.m_idxStart = m_buffer->GetIndexCount();
}
void FlushData(ref_ptr<GraphicsContext> context, BindingInfo const & info, void const * data, uint32_t count) override
{
if (m_overlay != nullptr && info.IsDynamic())
{
uint32_t offset = m_buffer->GetDynamicBufferOffset(info);
m_overlay->AddDynamicAttribute(info, offset, count);
}
m_buffer->UploadData(context, info, data, count);
}
void * GetIndexStorage(uint32_t size, uint32_t & startIndex) override
{
startIndex = m_buffer->GetStartIndexValue();
if (m_overlay == nullptr || !m_overlay->IndexesRequired())
{
m_indexStorage.Resize(size);
return m_indexStorage.GetRaw();
}
else
{
return m_overlay->IndexStorage(size);
}
}
void SubmitIndices(ref_ptr<GraphicsContext> context) override
{
if (m_overlay == nullptr || !m_overlay->IndexesRequired())
m_buffer->UploadIndices(context, m_indexStorage.GetRawConst(), m_indexStorage.Size());
}
uint32_t GetAvailableVertexCount() const override { return m_buffer->GetAvailableVertexCount(); }
uint32_t GetAvailableIndexCount() const override { return m_buffer->GetAvailableIndexCount(); }
void ChangeBuffer(ref_ptr<GraphicsContext> context) override { m_batcher->ChangeBuffer(context, make_ref(this)); }
RenderState const & GetState() const { return m_state; }
IndicesRange const & Finish()
{
if (!m_vaoChanged)
m_indicesRange.m_idxCount = m_buffer->GetIndexCount() - m_indicesRange.m_idxStart;
else
m_indicesRange = IndicesRange();
return m_indicesRange;
}
private:
RenderState const & m_state;
ref_ptr<OverlayHandle> m_overlay;
ref_ptr<Batcher> m_batcher;
ref_ptr<VertexArrayBuffer> m_buffer;
IndexStorage m_indexStorage;
IndicesRange m_indicesRange;
bool m_vaoChanged = false;
};
Batcher::Batcher(uint32_t indexBufferSize, uint32_t vertexBufferSize)
: m_indexBufferSize(indexBufferSize)
, m_vertexBufferSize(vertexBufferSize)
{}
Batcher::~Batcher()
{
m_buckets.clear();
}
void Batcher::InsertTriangleList(ref_ptr<GraphicsContext> context, RenderState const & state,
ref_ptr<AttributeProvider> params)
{
InsertTriangleList(context, state, params, nullptr);
}
IndicesRange Batcher::InsertTriangleList(ref_ptr<GraphicsContext> context, RenderState const & state,
ref_ptr<AttributeProvider> params, drape_ptr<OverlayHandle> && handle)
{
return InsertPrimitives<TriangleListBatch>(context, state, params, std::move(handle), 0 /* vertexStride */);
}
void Batcher::InsertTriangleStrip(ref_ptr<GraphicsContext> context, RenderState const & state,
ref_ptr<AttributeProvider> params)
{
InsertTriangleStrip(context, state, params, nullptr);
}
IndicesRange Batcher::InsertTriangleStrip(ref_ptr<GraphicsContext> context, RenderState const & state,
ref_ptr<AttributeProvider> params, drape_ptr<OverlayHandle> && handle)
{
return InsertPrimitives<TriangleStripBatch>(context, state, params, std::move(handle), 0 /* vertexStride */);
}
void Batcher::InsertTriangleFan(ref_ptr<GraphicsContext> context, RenderState const & state,
ref_ptr<AttributeProvider> params)
{
InsertTriangleFan(context, state, params, nullptr);
}
IndicesRange Batcher::InsertTriangleFan(ref_ptr<GraphicsContext> context, RenderState const & state,
ref_ptr<AttributeProvider> params, drape_ptr<OverlayHandle> && handle)
{
return InsertPrimitives<TriangleFanBatch>(context, state, params, std::move(handle), 0 /* vertexStride */);
}
void Batcher::InsertListOfStrip(ref_ptr<GraphicsContext> context, RenderState const & state,
ref_ptr<AttributeProvider> params, uint8_t vertexStride)
{
InsertListOfStrip(context, state, params, nullptr, vertexStride);
}
IndicesRange Batcher::InsertListOfStrip(ref_ptr<GraphicsContext> context, RenderState const & state,
ref_ptr<AttributeProvider> params, drape_ptr<OverlayHandle> && handle,
uint8_t vertexStride)
{
return InsertPrimitives<TriangleListOfStripBatch>(context, state, params, std::move(handle), vertexStride);
}
void Batcher::InsertLineStrip(ref_ptr<GraphicsContext> context, RenderState const & state,
ref_ptr<AttributeProvider> params)
{
InsertLineStrip(context, state, params, nullptr);
}
IndicesRange Batcher::InsertLineStrip(ref_ptr<GraphicsContext> context, RenderState const & state,
ref_ptr<AttributeProvider> params, drape_ptr<OverlayHandle> && handle)
{
return InsertPrimitives<LineStripBatch>(context, state, params, std::move(handle), 0 /* vertexStride */);
}
void Batcher::InsertLineRaw(ref_ptr<GraphicsContext> context, RenderState const & state,
ref_ptr<AttributeProvider> params, std::vector<int> const & indices)
{
InsertLineRaw(context, state, params, indices, nullptr);
}
IndicesRange Batcher::InsertLineRaw(ref_ptr<GraphicsContext> context, RenderState const & state,
ref_ptr<AttributeProvider> params, std::vector<int> const & indices,
drape_ptr<OverlayHandle> && handle)
{
return InsertPrimitives<LineRawBatch>(context, state, params, std::move(handle), 0 /* vertexStride */, indices);
}
void Batcher::StartSession(TFlushFn && flusher)
{
m_flushInterface = std::move(flusher);
}
void Batcher::EndSession(ref_ptr<GraphicsContext> context)
{
Flush(context);
m_flushInterface = TFlushFn();
}
void Batcher::ResetSession()
{
m_flushInterface = TFlushFn();
m_buckets.clear();
}
void Batcher::SetFeatureMinZoom(int minZoom)
{
m_featureMinZoom = minZoom;
for (auto const & bucket : m_buckets)
bucket.second->SetFeatureMinZoom(m_featureMinZoom);
}
void Batcher::SetBatcherHash(uint64_t batcherHash)
{
m_batcherHash = batcherHash;
}
void Batcher::ChangeBuffer(ref_ptr<GraphicsContext> context, ref_ptr<CallbacksWrapper> wrapper)
{
RenderState const & state = wrapper->GetState();
FinalizeBucket(context, state);
CHECK(m_buckets.find(state) == m_buckets.end(), ());
ref_ptr<RenderBucket> bucket = GetBucket(state);
wrapper->SetVAO(bucket->GetBuffer());
}
ref_ptr<RenderBucket> Batcher::GetBucket(RenderState const & state)
{
auto res = m_buckets.insert({state, nullptr});
if (res.second)
{
drape_ptr<VertexArrayBuffer> vao =
make_unique_dp<VertexArrayBuffer>(m_indexBufferSize, m_vertexBufferSize, m_batcherHash);
drape_ptr<RenderBucket> buffer = make_unique_dp<RenderBucket>(std::move(vao));
buffer->SetFeatureMinZoom(m_featureMinZoom);
res.first->second = std::move(buffer);
}
return make_ref(res.first->second);
}
void Batcher::FinalizeBucket(ref_ptr<GraphicsContext> context, RenderState const & state)
{
auto const it = m_buckets.find(state);
CHECK(it != m_buckets.end(), ("Have no bucket for finalize with given state"));
drape_ptr<RenderBucket> bucket = std::move(it->second);
m_buckets.erase(it);
bucket->GetBuffer()->Preflush(context);
m_flushInterface(state, std::move(bucket));
}
void Batcher::Flush(ref_ptr<GraphicsContext> context)
{
ASSERT(m_flushInterface != NULL, ());
std::for_each(m_buckets.begin(), m_buckets.end(), [this, context](TBuckets::value_type & bucket)
{
ASSERT(bucket.second != nullptr, ());
bucket.second->GetBuffer()->Preflush(context);
m_flushInterface(bucket.first, std::move(bucket.second));
});
m_buckets.clear();
}
template <typename TBatcher, typename... TArgs>
IndicesRange Batcher::InsertPrimitives(ref_ptr<GraphicsContext> context, RenderState const & state,
ref_ptr<AttributeProvider> params, drape_ptr<OverlayHandle> && transferHandle,
uint8_t vertexStride, TArgs... batcherArgs)
{
ref_ptr<VertexArrayBuffer> vao = GetBucket(state)->GetBuffer();
IndicesRange range;
drape_ptr<OverlayHandle> handle = std::move(transferHandle);
{
Batcher::CallbacksWrapper wrapper(state, make_ref(handle), make_ref(this));
wrapper.SetVAO(vao);
TBatcher batch(wrapper, batcherArgs...);
batch.SetCanDivideStreams(handle == nullptr);
batch.SetVertexStride(vertexStride);
batch.BatchData(context, params);
range = wrapper.Finish();
}
if (handle != nullptr)
GetBucket(state)->AddOverlayHandle(std::move(handle));
return range;
}
Batcher * BatcherFactory::GetNew() const
{
return new Batcher(m_indexBufferSize, m_vertexBufferSize);
}
SessionGuard::SessionGuard(ref_ptr<GraphicsContext> context, Batcher & batcher, Batcher::TFlushFn && flusher)
: m_context(context)
, m_batcher(batcher)
{
m_batcher.StartSession(std::move(flusher));
}
SessionGuard::~SessionGuard()
{
m_batcher.EndSession(m_context);
}
} // namespace dp

125
libs/drape/batcher.hpp Normal file
View file

@ -0,0 +1,125 @@
#pragma once
#include "drape/attribute_provider.hpp"
#include "drape/graphics_context.hpp"
#include "drape/overlay_handle.hpp"
#include "drape/pointers.hpp"
#include "drape/render_bucket.hpp"
#include "drape/render_state.hpp"
#include "drape/vertex_array_buffer.hpp"
#include "base/macros.hpp"
#include <functional>
#include <map>
#include <vector>
namespace dp
{
class RenderBucket;
class AttributeProvider;
class OverlayHandle;
class Batcher
{
public:
static uint32_t const IndexPerTriangle = 3;
static uint32_t const IndexPerQuad = 6;
static uint32_t const VertexPerQuad = 4;
Batcher(uint32_t indexBufferSize, uint32_t vertexBufferSize);
~Batcher();
void InsertTriangleList(ref_ptr<GraphicsContext> context, RenderState const & state,
ref_ptr<AttributeProvider> params);
IndicesRange InsertTriangleList(ref_ptr<GraphicsContext> context, RenderState const & state,
ref_ptr<AttributeProvider> params, drape_ptr<OverlayHandle> && handle);
void InsertTriangleStrip(ref_ptr<GraphicsContext> context, RenderState const & state,
ref_ptr<AttributeProvider> params);
IndicesRange InsertTriangleStrip(ref_ptr<GraphicsContext> context, RenderState const & state,
ref_ptr<AttributeProvider> params, drape_ptr<OverlayHandle> && handle);
void InsertTriangleFan(ref_ptr<GraphicsContext> context, RenderState const & state,
ref_ptr<AttributeProvider> params);
IndicesRange InsertTriangleFan(ref_ptr<GraphicsContext> context, RenderState const & state,
ref_ptr<AttributeProvider> params, drape_ptr<OverlayHandle> && handle);
void InsertListOfStrip(ref_ptr<GraphicsContext> context, RenderState const & state, ref_ptr<AttributeProvider> params,
uint8_t vertexStride);
IndicesRange InsertListOfStrip(ref_ptr<GraphicsContext> context, RenderState const & state,
ref_ptr<AttributeProvider> params, drape_ptr<OverlayHandle> && handle,
uint8_t vertexStride);
void InsertLineStrip(ref_ptr<GraphicsContext> context, RenderState const & state, ref_ptr<AttributeProvider> params);
IndicesRange InsertLineStrip(ref_ptr<GraphicsContext> context, RenderState const & state,
ref_ptr<AttributeProvider> params, drape_ptr<OverlayHandle> && handle);
void InsertLineRaw(ref_ptr<GraphicsContext> context, RenderState const & state, ref_ptr<AttributeProvider> params,
std::vector<int> const & indices);
IndicesRange InsertLineRaw(ref_ptr<GraphicsContext> context, RenderState const & state,
ref_ptr<AttributeProvider> params, std::vector<int> const & indices,
drape_ptr<OverlayHandle> && handle);
using TFlushFn = std::function<void(RenderState const &, drape_ptr<RenderBucket> &&)>;
void StartSession(TFlushFn && flusher);
void EndSession(ref_ptr<GraphicsContext> context);
void ResetSession();
void SetBatcherHash(uint64_t batcherHash);
void SetFeatureMinZoom(int minZoom);
private:
template <typename TBatcher, typename... TArgs>
IndicesRange InsertPrimitives(ref_ptr<GraphicsContext> context, RenderState const & state,
ref_ptr<AttributeProvider> params, drape_ptr<OverlayHandle> && transferHandle,
uint8_t vertexStride, TArgs... batcherArgs);
class CallbacksWrapper;
void ChangeBuffer(ref_ptr<GraphicsContext> context, ref_ptr<CallbacksWrapper> wrapper);
ref_ptr<RenderBucket> GetBucket(RenderState const & state);
void FinalizeBucket(ref_ptr<GraphicsContext> context, RenderState const & state);
void Flush(ref_ptr<GraphicsContext> context);
uint32_t const m_indexBufferSize;
uint32_t const m_vertexBufferSize;
uint64_t m_batcherHash = 0;
TFlushFn m_flushInterface;
using TBuckets = std::map<RenderState, drape_ptr<RenderBucket>>;
TBuckets m_buckets;
int m_featureMinZoom = 0;
};
class BatcherFactory
{
public:
BatcherFactory(uint32_t indexBufferSize, uint32_t vertexBufferSize)
: m_indexBufferSize(indexBufferSize)
, m_vertexBufferSize(vertexBufferSize)
{}
Batcher * GetNew() const;
private:
uint32_t const m_indexBufferSize;
uint32_t const m_vertexBufferSize;
};
class SessionGuard
{
public:
SessionGuard(ref_ptr<GraphicsContext> context, Batcher & batcher, Batcher::TFlushFn && flusher);
~SessionGuard();
private:
ref_ptr<GraphicsContext> m_context;
Batcher & m_batcher;
DISALLOW_COPY_AND_MOVE(SessionGuard);
};
} // namespace dp

View file

@ -0,0 +1,639 @@
#include "drape/batcher_helpers.hpp"
#include "drape/attribute_provider.hpp"
#include "drape/cpu_buffer.hpp"
#include "drape/index_storage.hpp"
#include "base/assert.hpp"
#include "base/math.hpp"
#include <algorithm>
namespace dp
{
namespace
{
template <typename T>
T CyclicClamp(T const x, T const xmin, T const xmax)
{
if (x > xmax)
return xmin;
if (x < xmin)
return xmax;
return x;
}
bool IsEnoughMemory(uint32_t avVertex, uint32_t existVertex, uint32_t avIndex, uint32_t existIndex)
{
return avVertex >= existVertex && avIndex >= existIndex;
}
template <typename TGenerator>
void GenerateIndices(void * indexStorage, uint32_t count, uint32_t startIndex)
{
GenerateIndices<TGenerator>(indexStorage, count, TGenerator(startIndex));
}
template <typename TGenerator>
void GenerateIndices(void * indexStorage, uint32_t count, TGenerator const & generator)
{
if (dp::IndexStorage::IsSupported32bit())
{
auto pIndexStorage = static_cast<uint32_t *>(indexStorage);
std::generate(pIndexStorage, pIndexStorage + count, generator);
}
else
{
auto pIndexStorage = static_cast<uint16_t *>(indexStorage);
std::generate(pIndexStorage, pIndexStorage + count, generator);
}
}
class IndexGenerator
{
public:
explicit IndexGenerator(uint32_t startIndex) : m_startIndex(startIndex), m_counter(0), m_minStripCounter(0) {}
protected:
uint32_t GetCounter() { return m_counter++; }
void ResetCounter()
{
m_counter = 0;
m_minStripCounter = 0;
}
uint32_t const m_startIndex;
int16_t GetCWNormalizer()
{
int16_t tmp = m_minStripCounter;
m_minStripCounter = static_cast<uint8_t>(CyclicClamp(m_minStripCounter + 1, 0, 5));
switch (tmp)
{
case 4: return 1;
case 5: return -1;
default: return 0;
}
}
private:
uint32_t m_counter;
uint8_t m_minStripCounter;
};
class ListIndexGenerator : public IndexGenerator
{
public:
explicit ListIndexGenerator(uint32_t startIndex) : IndexGenerator(startIndex) {}
uint32_t operator()() { return m_startIndex + GetCounter(); }
};
class StripIndexGenerator : public IndexGenerator
{
public:
explicit StripIndexGenerator(uint32_t startIndex) : IndexGenerator(startIndex) {}
uint32_t operator()()
{
uint32_t const counter = GetCounter();
return m_startIndex + counter - 2 * (counter / 3) + GetCWNormalizer();
}
};
class LineStripIndexGenerator : public IndexGenerator
{
public:
explicit LineStripIndexGenerator(uint32_t startIndex) : IndexGenerator(startIndex) {}
uint32_t operator()()
{
uint32_t const counter = GetCounter();
uint32_t const result = m_startIndex + m_counter;
if (counter % 2 == 0)
m_counter++;
return result;
}
private:
uint32_t m_counter = 0;
};
class LineRawIndexGenerator : public IndexGenerator
{
public:
LineRawIndexGenerator(uint32_t startIndex, std::vector<int> const & indices)
: IndexGenerator(startIndex)
, m_indices(indices)
{}
uint32_t operator()()
{
uint32_t const counter = GetCounter();
ASSERT_LESS(counter, m_indices.size(), ());
return static_cast<uint32_t>(m_startIndex + m_indices[counter]);
}
private:
std::vector<int> const & m_indices;
};
class FanIndexGenerator : public IndexGenerator
{
public:
explicit FanIndexGenerator(uint32_t startIndex) : IndexGenerator(startIndex) {}
uint32_t operator()()
{
uint32_t const counter = GetCounter();
if ((counter % 3) == 0)
return m_startIndex;
return m_startIndex + counter - 2 * (counter / 3);
}
};
class ListOfStripGenerator : public IndexGenerator
{
public:
ListOfStripGenerator(uint32_t startIndex, uint32_t vertexStride, uint32_t indexPerStrip)
: IndexGenerator(startIndex)
, m_vertexStride(vertexStride)
, m_indexPerStrip(indexPerStrip)
, m_base(0)
{}
uint32_t operator()()
{
uint32_t const counter = GetCounter();
uint32_t const result = m_startIndex + m_base + counter - 2 * (counter / 3) + GetCWNormalizer();
if (counter + 1 == m_indexPerStrip)
{
m_base += m_vertexStride;
ResetCounter();
}
return result;
}
private:
uint32_t m_vertexStride;
uint32_t m_indexPerStrip;
uint32_t m_base;
};
} // namespace
UniversalBatch::UniversalBatch(BatchCallbacks & callbacks, uint8_t minVerticesCount, uint8_t minIndicesCount)
: m_callbacks(callbacks)
, m_canDivideStreams(true)
, m_minVerticesCount(minVerticesCount)
, m_minIndicesCount(minIndicesCount)
{}
void UniversalBatch::SetCanDivideStreams(bool canDivide)
{
m_canDivideStreams = canDivide;
}
bool UniversalBatch::CanDivideStreams() const
{
return m_canDivideStreams;
}
void UniversalBatch::SetVertexStride(uint8_t vertexStride)
{
m_vertexStride = vertexStride;
}
void UniversalBatch::FlushData(ref_ptr<GraphicsContext> context, ref_ptr<AttributeProvider> streams,
uint32_t vertexCount) const
{
for (uint8_t i = 0; i < streams->GetStreamCount(); ++i)
FlushData(context, streams->GetBindingInfo(i), streams->GetRawPointer(i), vertexCount);
}
void UniversalBatch::FlushData(ref_ptr<GraphicsContext> context, BindingInfo const & info, void const * data,
uint32_t elementCount) const
{
m_callbacks.FlushData(context, info, data, elementCount);
}
void * UniversalBatch::GetIndexStorage(uint32_t indexCount, uint32_t & startIndex)
{
return m_callbacks.GetIndexStorage(indexCount, startIndex);
}
void UniversalBatch::SubmitIndices(ref_ptr<GraphicsContext> context)
{
m_callbacks.SubmitIndices(context);
}
uint32_t UniversalBatch::GetAvailableVertexCount() const
{
return m_callbacks.GetAvailableVertexCount();
}
uint32_t UniversalBatch::GetAvailableIndexCount() const
{
return m_callbacks.GetAvailableIndexCount();
}
void UniversalBatch::ChangeBuffer(ref_ptr<GraphicsContext> context) const
{
return m_callbacks.ChangeBuffer(context);
}
uint8_t UniversalBatch::GetVertexStride() const
{
return m_vertexStride;
}
bool UniversalBatch::IsBufferFilled(uint32_t availableVerticesCount, uint32_t availableIndicesCount) const
{
return availableVerticesCount < m_minVerticesCount || availableIndicesCount < m_minIndicesCount;
}
TriangleListBatch::TriangleListBatch(BatchCallbacks & callbacks)
: TBase(callbacks, 3 /* minVerticesCount */, 3 /* minIndicesCount */)
{}
void TriangleListBatch::BatchData(ref_ptr<GraphicsContext> context, ref_ptr<AttributeProvider> streams)
{
while (streams->IsDataExists())
{
if (IsBufferFilled(GetAvailableVertexCount(), GetAvailableIndexCount()))
ChangeBuffer(context);
uint32_t avVertex = GetAvailableVertexCount();
uint32_t avIndex = GetAvailableIndexCount();
uint32_t vertexCount = streams->GetVertexCount();
if (CanDivideStreams())
{
vertexCount = std::min(vertexCount, avVertex);
vertexCount = std::min(vertexCount, avIndex);
ASSERT(vertexCount >= 3, ());
vertexCount -= vertexCount % 3;
}
else if (!IsEnoughMemory(avVertex, vertexCount, avIndex, vertexCount))
{
ChangeBuffer(context);
avVertex = GetAvailableVertexCount();
avIndex = GetAvailableIndexCount();
ASSERT(IsEnoughMemory(avVertex, vertexCount, avIndex, vertexCount), ());
ASSERT(vertexCount % 3 == 0, ());
}
uint32_t startIndex = 0;
void * indicesStorage = GetIndexStorage(vertexCount, startIndex);
GenerateIndices<ListIndexGenerator>(indicesStorage, vertexCount, startIndex);
SubmitIndices(context);
FlushData(context, streams, vertexCount);
streams->Advance(vertexCount);
}
}
LineStripBatch::LineStripBatch(BatchCallbacks & callbacks)
: TBase(callbacks, 2 /* minVerticesCount */, 2 /* minIndicesCount */)
{}
void LineStripBatch::BatchData(ref_ptr<GraphicsContext> context, ref_ptr<AttributeProvider> streams)
{
while (streams->IsDataExists())
{
if (IsBufferFilled(GetAvailableVertexCount(), GetAvailableIndexCount()))
ChangeBuffer(context);
uint32_t avVertex = GetAvailableVertexCount();
uint32_t avIndex = GetAvailableIndexCount();
uint32_t vertexCount = streams->GetVertexCount();
ASSERT_GREATER_OR_EQUAL(vertexCount, 2, ());
uint32_t indexCount = (vertexCount - 1) * 2;
if (!IsEnoughMemory(avVertex, vertexCount, avIndex, indexCount))
{
ChangeBuffer(context);
avVertex = GetAvailableVertexCount();
avIndex = GetAvailableIndexCount();
ASSERT(IsEnoughMemory(avVertex, vertexCount, avIndex, indexCount), ());
}
uint32_t startIndex = 0;
void * indicesStorage = GetIndexStorage(indexCount, startIndex);
GenerateIndices<LineStripIndexGenerator>(indicesStorage, indexCount, startIndex);
SubmitIndices(context);
FlushData(context, streams, vertexCount);
streams->Advance(vertexCount);
}
}
LineRawBatch::LineRawBatch(BatchCallbacks & callbacks, std::vector<int> const & indices)
: TBase(callbacks, 2 /* minVerticesCount */, 2 /* minIndicesCount */)
, m_indices(indices)
{}
void LineRawBatch::BatchData(ref_ptr<GraphicsContext> context, ref_ptr<AttributeProvider> streams)
{
while (streams->IsDataExists())
{
if (IsBufferFilled(GetAvailableVertexCount(), GetAvailableIndexCount()))
ChangeBuffer(context);
uint32_t avVertex = GetAvailableVertexCount();
uint32_t avIndex = GetAvailableIndexCount();
uint32_t vertexCount = streams->GetVertexCount();
CHECK_GREATER_OR_EQUAL(vertexCount, 2, (vertexCount));
auto const indexCount = static_cast<uint32_t>(m_indices.size());
if (!IsEnoughMemory(avVertex, vertexCount, avIndex, indexCount))
{
ChangeBuffer(context);
avVertex = GetAvailableVertexCount();
avIndex = GetAvailableIndexCount();
CHECK(IsEnoughMemory(avVertex, vertexCount, avIndex, indexCount), (avVertex, vertexCount, avIndex, indexCount));
}
uint32_t startIndex = 0;
void * indicesStorage = GetIndexStorage(indexCount, startIndex);
LineRawIndexGenerator generator(startIndex, m_indices);
GenerateIndices(indicesStorage, indexCount, generator);
SubmitIndices(context);
FlushData(context, streams, vertexCount);
streams->Advance(vertexCount);
}
}
FanStripHelper::FanStripHelper(BatchCallbacks & callbacks)
: TBase(callbacks, 3 /* minVerticesCount */, 3 /* minIndicesCount */)
, m_isFullUploaded(false)
{}
uint32_t FanStripHelper::BatchIndexes(ref_ptr<GraphicsContext> context, uint32_t vertexCount)
{
uint32_t avVertex = GetAvailableVertexCount();
uint32_t avIndex = GetAvailableIndexCount();
uint32_t batchVertexCount = 0;
uint32_t batchIndexCount = 0;
CalcBatchPortion(vertexCount, avVertex, avIndex, batchVertexCount, batchIndexCount);
if (!IsFullUploaded() && !CanDivideStreams())
{
ChangeBuffer(context);
avVertex = GetAvailableVertexCount();
avIndex = GetAvailableIndexCount();
CalcBatchPortion(vertexCount, avVertex, avIndex, batchVertexCount, batchIndexCount);
ASSERT(IsFullUploaded(), ());
}
uint32_t startIndex = 0;
void * pIndexStorage = GetIndexStorage(batchIndexCount, startIndex);
GenerateIndexes(pIndexStorage, batchIndexCount, startIndex);
SubmitIndices(context);
return batchVertexCount;
}
void FanStripHelper::CalcBatchPortion(uint32_t vertexCount, uint32_t avVertex, uint32_t avIndex,
uint32_t & batchVertexCount, uint32_t & batchIndexCount)
{
uint32_t const indexCount = VtoICount(vertexCount);
batchVertexCount = vertexCount;
batchIndexCount = indexCount;
m_isFullUploaded = true;
if (vertexCount > avVertex || indexCount > avIndex)
{
uint32_t alignedAvVertex = AlignVCount(avVertex);
uint32_t alignedAvIndex = AlignICount(avIndex);
uint32_t indexCountForAvailableVertexCount = VtoICount(alignedAvVertex);
if (indexCountForAvailableVertexCount <= alignedAvIndex)
{
batchVertexCount = alignedAvVertex;
batchIndexCount = indexCountForAvailableVertexCount;
}
else
{
batchIndexCount = alignedAvIndex;
batchVertexCount = ItoVCount(batchIndexCount);
}
m_isFullUploaded = false;
}
}
bool FanStripHelper::IsFullUploaded() const
{
return m_isFullUploaded;
}
uint32_t FanStripHelper::VtoICount(uint32_t vCount) const
{
return 3 * (vCount - 2);
}
uint32_t FanStripHelper::ItoVCount(uint32_t iCount) const
{
return iCount / 3 + 2;
}
uint32_t FanStripHelper::AlignVCount(uint32_t vCount) const
{
return vCount;
}
uint32_t FanStripHelper::AlignICount(uint32_t iCount) const
{
return iCount - iCount % 3;
}
TriangleStripBatch::TriangleStripBatch(BatchCallbacks & callbacks) : TBase(callbacks) {}
void TriangleStripBatch::BatchData(ref_ptr<GraphicsContext> context, ref_ptr<AttributeProvider> streams)
{
while (streams->IsDataExists())
{
if (IsBufferFilled(GetAvailableVertexCount(), GetAvailableIndexCount()))
ChangeBuffer(context);
uint32_t const batchVertexCount = BatchIndexes(context, streams->GetVertexCount());
FlushData(context, streams, batchVertexCount);
uint32_t const advanceCount = IsFullUploaded() ? batchVertexCount : (batchVertexCount - 2);
streams->Advance(advanceCount);
}
}
void TriangleStripBatch::GenerateIndexes(void * indexStorage, uint32_t count, uint32_t startIndex) const
{
GenerateIndices<StripIndexGenerator>(indexStorage, count, startIndex);
}
TriangleFanBatch::TriangleFanBatch(BatchCallbacks & callbacks) : TBase(callbacks) {}
/*
* What happens here
*
* We try to pack TriangleFan on GPU indexed like triangle list.
* If we have enough memory in VertexArrayBuffer to store all data from params, we just copy it
*
* If we have not enough memory we broke data on parts.
* On first iteration we create CPUBuffer for each separate atribute
* in params and copy to it first vertex of fan. This vertex will be need
* when we will upload second part of data.
*
* Than we copy vertex data on GPU as much as we can and move params cursor on
* "uploaded vertex count" - 1. This last vertex will be used for uploading next part of data
*
* On second iteration we need upload first vertex of fan that stored in cpuBuffers and than upload
* second part of data. But to avoid 2 separate call of glBufferSubData we at first do a copy of
* data from params to cpuBuffer and than copy continuous block of memory from cpuBuffer
*/
void TriangleFanBatch::BatchData(ref_ptr<GraphicsContext> context, ref_ptr<AttributeProvider> streams)
{
std::vector<CPUBuffer> cpuBuffers;
while (streams->IsDataExists())
{
if (IsBufferFilled(GetAvailableVertexCount(), GetAvailableIndexCount()))
ChangeBuffer(context);
uint32_t vertexCount = streams->GetVertexCount();
uint32_t batchVertexCount = BatchIndexes(context, vertexCount);
if (!cpuBuffers.empty())
{
// if cpuBuffers not empty than on previous iteration we not move data on gpu
// and in cpuBuffers stored first vertex of fan.
// To avoid two separate call of glBufferSubData
// (for first vertex and for next part of data)
// we at first copy next part of data into
// cpuBuffers, and than copy it from cpuBuffers to GPU
for (size_t i = 0; i < streams->GetStreamCount(); ++i)
{
CPUBuffer & cpuBuffer = cpuBuffers[i];
ASSERT(cpuBuffer.GetCurrentElementNumber() == 1, ());
cpuBuffer.UploadData(streams->GetRawPointer(i), batchVertexCount);
// now in cpuBuffer we have correct "fan" created from second part of data
// first vertex of cpuBuffer if the first vertex of params, second vertex is
// the last vertex of previous uploaded data. We copy this data on GPU.
FlushData(context, streams->GetBindingInfo(i), cpuBuffer.Data(), batchVertexCount + 1);
}
uint32_t advanceCount = batchVertexCount;
if (!IsFullUploaded())
{
// not all data was moved on gpu and last vertex of fan
// will need on second iteration
advanceCount -= 1;
}
streams->Advance(advanceCount);
}
else // if m_cpuBuffer empty than it's first iteration
if (IsFullUploaded())
{
// We can upload all input data as one peace. For upload we need only one iteration
FlushData(context, streams, batchVertexCount);
streams->Advance(batchVertexCount);
}
else
{
// for each stream we must create CPU buffer.
// Copy first vertex of fan into cpuBuffer for next iterations
// Than move first part of data on GPU
cpuBuffers.reserve(streams->GetStreamCount());
for (size_t i = 0; i < streams->GetStreamCount(); ++i)
{
BindingInfo const & binding = streams->GetBindingInfo(i);
void const * rawDataPointer = streams->GetRawPointer(i);
FlushData(context, binding, rawDataPointer, batchVertexCount);
// "(vertexCount + 1) - batchVertexCount" we allocate CPUBuffer on all remaining data
// + first vertex of fan, that must be duplicate in the next buffer
// + last vertex of currently uploaded data.
cpuBuffers.emplace_back(binding.GetElementSize(), (vertexCount + 2) - batchVertexCount);
CPUBuffer & cpuBuffer = cpuBuffers.back();
cpuBuffer.UploadData(rawDataPointer, 1);
// Move cpu buffer cursor on second element of buffer.
// On next iteration first vertex of fan will be also available
cpuBuffer.Seek(1);
}
// advance on uploadVertexCount - 1 to copy last vertex also into next VAO with
// first vertex of data from CPUBuffers
streams->Advance(batchVertexCount - 1);
}
}
}
void TriangleFanBatch::GenerateIndexes(void * indexStorage, uint32_t count, uint32_t startIndex) const
{
GenerateIndices<FanIndexGenerator>(indexStorage, count, startIndex);
}
TriangleListOfStripBatch::TriangleListOfStripBatch(BatchCallbacks & callbacks) : TBase(callbacks) {}
void TriangleListOfStripBatch::BatchData(ref_ptr<GraphicsContext> context, ref_ptr<AttributeProvider> streams)
{
while (streams->IsDataExists())
{
if (IsBufferFilled(GetAvailableVertexCount(), GetAvailableIndexCount()))
ChangeBuffer(context);
uint32_t const batchVertexCount = BatchIndexes(context, streams->GetVertexCount());
FlushData(context, streams, batchVertexCount);
streams->Advance(batchVertexCount);
}
}
bool TriangleListOfStripBatch::IsBufferFilled(uint32_t availableVerticesCount, uint32_t availableIndicesCount) const
{
uint8_t const vertexStride = GetVertexStride();
ASSERT_GREATER_OR_EQUAL(vertexStride, 4, ());
uint32_t const indicesPerStride = TBase::VtoICount(vertexStride);
return availableVerticesCount < vertexStride || availableIndicesCount < indicesPerStride;
}
uint32_t TriangleListOfStripBatch::VtoICount(uint32_t vCount) const
{
uint8_t const vertexStride = GetVertexStride();
ASSERT_GREATER_OR_EQUAL(vertexStride, 4, ());
ASSERT_EQUAL(vCount % vertexStride, 0, ());
uint32_t const striptCount = vCount / vertexStride;
return striptCount * TBase::VtoICount(vertexStride);
}
uint32_t TriangleListOfStripBatch::ItoVCount(uint32_t iCount) const
{
uint8_t const vertexStride = GetVertexStride();
ASSERT_GREATER_OR_EQUAL(vertexStride, 4, ());
ASSERT_EQUAL(iCount % 3, 0, ());
return vertexStride * iCount / TBase::VtoICount(vertexStride);
}
uint32_t TriangleListOfStripBatch::AlignVCount(uint32_t vCount) const
{
return vCount - vCount % GetVertexStride();
}
uint32_t TriangleListOfStripBatch::AlignICount(uint32_t iCount) const
{
uint8_t const vertexStride = GetVertexStride();
ASSERT_GREATER_OR_EQUAL(vertexStride, 4, ());
uint32_t const indicesPerStride = TBase::VtoICount(vertexStride);
return iCount - iCount % indicesPerStride;
}
void TriangleListOfStripBatch::GenerateIndexes(void * indexStorage, uint32_t count, uint32_t startIndex) const
{
uint8_t const vertexStride = GetVertexStride();
GenerateIndices(indexStorage, count, ListOfStripGenerator(startIndex, vertexStride, VtoICount(vertexStride)));
}
} // namespace dp

View file

@ -0,0 +1,157 @@
#pragma once
#include "drape/graphics_context.hpp"
#include "drape/pointers.hpp"
#include <vector>
namespace dp
{
class AttributeProvider;
class BindingInfo;
class BatchCallbacks
{
public:
virtual ~BatchCallbacks() = default;
virtual void FlushData(ref_ptr<GraphicsContext> context, BindingInfo const & binding, void const * data,
uint32_t count) = 0;
virtual void * GetIndexStorage(uint32_t indexCount, uint32_t & startIndex) = 0;
virtual void SubmitIndices(ref_ptr<GraphicsContext> context) = 0;
virtual uint32_t GetAvailableVertexCount() const = 0;
virtual uint32_t GetAvailableIndexCount() const = 0;
virtual void ChangeBuffer(ref_ptr<GraphicsContext> context) = 0;
};
class UniversalBatch
{
public:
UniversalBatch(BatchCallbacks & callbacks, uint8_t minVerticesCount, uint8_t minIndicesCount);
virtual ~UniversalBatch() = default;
virtual void BatchData(ref_ptr<GraphicsContext> context, ref_ptr<AttributeProvider> streams) = 0;
void SetCanDivideStreams(bool canDivide);
bool CanDivideStreams() const;
void SetVertexStride(uint8_t vertexStride);
protected:
void FlushData(ref_ptr<GraphicsContext> context, ref_ptr<AttributeProvider> streams, uint32_t vertexCount) const;
void FlushData(ref_ptr<GraphicsContext> context, BindingInfo const & info, void const * data,
uint32_t elementCount) const;
void * GetIndexStorage(uint32_t indexCount, uint32_t & startIndex);
void SubmitIndices(ref_ptr<GraphicsContext> context);
uint32_t GetAvailableVertexCount() const;
uint32_t GetAvailableIndexCount() const;
void ChangeBuffer(ref_ptr<GraphicsContext> context) const;
uint8_t GetVertexStride() const;
virtual bool IsBufferFilled(uint32_t availableVerticesCount, uint32_t availableIndicesCount) const;
private:
BatchCallbacks & m_callbacks;
bool m_canDivideStreams;
uint8_t m_vertexStride;
uint8_t const m_minVerticesCount;
uint8_t const m_minIndicesCount;
};
class TriangleListBatch : public UniversalBatch
{
using TBase = UniversalBatch;
public:
explicit TriangleListBatch(BatchCallbacks & callbacks);
void BatchData(ref_ptr<GraphicsContext> context, ref_ptr<AttributeProvider> streams) override;
};
class LineStripBatch : public UniversalBatch
{
using TBase = UniversalBatch;
public:
explicit LineStripBatch(BatchCallbacks & callbacks);
void BatchData(ref_ptr<GraphicsContext> context, ref_ptr<AttributeProvider> streams) override;
};
class LineRawBatch : public UniversalBatch
{
using TBase = UniversalBatch;
public:
LineRawBatch(BatchCallbacks & callbacks, std::vector<int> const & indices);
void BatchData(ref_ptr<GraphicsContext> context, ref_ptr<AttributeProvider> streams) override;
private:
std::vector<int> const & m_indices;
};
class FanStripHelper : public UniversalBatch
{
using TBase = UniversalBatch;
public:
explicit FanStripHelper(BatchCallbacks & callbacks);
protected:
uint32_t BatchIndexes(ref_ptr<GraphicsContext> context, uint32_t vertexCount);
void CalcBatchPortion(uint32_t vertexCount, uint32_t avVertex, uint32_t avIndex, uint32_t & batchVertexCount,
uint32_t & batchIndexCount);
bool IsFullUploaded() const;
virtual uint32_t VtoICount(uint32_t vCount) const;
virtual uint32_t ItoVCount(uint32_t iCount) const;
virtual uint32_t AlignVCount(uint32_t vCount) const;
virtual uint32_t AlignICount(uint32_t vCount) const;
virtual void GenerateIndexes(void * indexStorage, uint32_t count, uint32_t startIndex) const = 0;
private:
bool m_isFullUploaded;
};
class TriangleStripBatch : public FanStripHelper
{
using TBase = FanStripHelper;
public:
explicit TriangleStripBatch(BatchCallbacks & callbacks);
void BatchData(ref_ptr<GraphicsContext> context, ref_ptr<AttributeProvider> streams) override;
protected:
void GenerateIndexes(void * indexStorage, uint32_t count, uint32_t startIndex) const override;
};
class TriangleFanBatch : public FanStripHelper
{
using TBase = FanStripHelper;
public:
explicit TriangleFanBatch(BatchCallbacks & callbacks);
void BatchData(ref_ptr<GraphicsContext> context, ref_ptr<AttributeProvider> streams) override;
protected:
void GenerateIndexes(void * indexStorage, uint32_t count, uint32_t startIndex) const override;
};
class TriangleListOfStripBatch : public FanStripHelper
{
using TBase = FanStripHelper;
public:
explicit TriangleListOfStripBatch(BatchCallbacks & callbacks);
void BatchData(ref_ptr<GraphicsContext> context, ref_ptr<AttributeProvider> streams) override;
protected:
bool IsBufferFilled(uint32_t availableVerticesCount, uint32_t availableIndicesCount) const override;
uint32_t VtoICount(uint32_t vCount) const override;
uint32_t ItoVCount(uint32_t iCount) const override;
uint32_t AlignVCount(uint32_t vCount) const override;
uint32_t AlignICount(uint32_t iCount) const override;
void GenerateIndexes(void * indexStorage, uint32_t count, uint32_t startIndex) const override;
};
} // namespace dp

134
libs/drape/binding_info.cpp Normal file
View file

@ -0,0 +1,134 @@
#include "drape/binding_info.hpp"
#include "drape/gl_includes.hpp"
#include "base/assert.hpp"
namespace dp
{
namespace
{
uint16_t SizeOfType(glConst type)
{
if (type == gl_const::GLByteType || type == gl_const::GLUnsignedByteType)
return sizeof(GLbyte);
else if (type == gl_const::GLShortType || type == gl_const::GLUnsignedShortType)
return sizeof(GLshort);
else if (type == gl_const::GLIntType || type == gl_const::GLUnsignedIntType)
return sizeof(GLint);
else if (type == gl_const::GLFloatType)
return sizeof(GLfloat);
ASSERT(false, ());
return 0;
}
} // namespace
bool BindingDecl::operator==(BindingDecl const & other) const
{
return m_attributeName == other.m_attributeName && m_componentCount == other.m_componentCount &&
m_componentType == other.m_componentType && m_stride == other.m_stride && m_offset == other.m_offset;
}
bool BindingDecl::operator!=(BindingDecl const & other) const
{
return !operator==(other);
}
bool BindingDecl::operator<(BindingDecl const & other) const
{
if (m_attributeName != other.m_attributeName)
return m_attributeName < other.m_attributeName;
if (m_componentCount != other.m_componentCount)
return m_componentCount < other.m_componentCount;
if (m_componentType != other.m_componentType)
return m_componentType < other.m_componentType;
if (m_stride != other.m_stride)
return m_stride < other.m_stride;
return m_offset < other.m_offset;
}
BindingInfo::BindingInfo() : m_info(0) {}
BindingInfo::BindingInfo(uint8_t count, uint8_t id) : m_info((static_cast<uint16_t>(count) << 8) | id)
{
CHECK_LESS_OR_EQUAL(count, kMaxBindingDecl, ());
}
uint8_t BindingInfo::GetCount() const
{
return static_cast<uint8_t>((m_info & 0xFF00) >> 8);
}
uint8_t BindingInfo::GetID() const
{
return static_cast<uint8_t>(m_info & 0xFF);
}
BindingDecl const & BindingInfo::GetBindingDecl(uint16_t index) const
{
ASSERT_LESS(index, GetCount(), ());
return m_bindings[index];
}
BindingDecl & BindingInfo::GetBindingDecl(uint16_t index)
{
ASSERT_LESS(index, GetCount(), ());
return m_bindings[index];
}
uint16_t BindingInfo::GetElementSize() const
{
if (GetCount() == 0)
return 0;
uint8_t stride = m_bindings[0].m_stride;
if (stride != 0)
return stride;
int calcStride = 0;
for (uint16_t i = 0; i < GetCount(); ++i)
calcStride += (m_bindings[i].m_componentCount * SizeOfType(m_bindings[i].m_componentType));
return static_cast<uint16_t>(calcStride);
}
bool BindingInfo::IsDynamic() const
{
return GetID() > 0;
}
bool BindingInfo::operator==(BindingInfo const & other) const
{
if (m_info != other.m_info)
return false;
for (uint16_t i = 0; i < GetCount(); ++i)
{
BindingDecl const & thisDecl = m_bindings[i];
BindingDecl const & otherDecl = other.m_bindings[i];
if (thisDecl != otherDecl)
return false;
}
return true;
}
bool BindingInfo::operator!=(BindingInfo const & other) const
{
return !operator==(other);
}
bool BindingInfo::operator<(BindingInfo const & other) const
{
if (m_info != other.m_info)
return m_info < other.m_info;
for (uint16_t i = 0; i < GetCount(); ++i)
{
BindingDecl const & thisDecl = m_bindings[i];
BindingDecl const & otherDecl = other.m_bindings[i];
if (thisDecl != otherDecl)
return thisDecl < otherDecl;
}
return false;
}
} // namespace dp

View file

@ -0,0 +1,85 @@
#pragma once
#include "drape/gl_constants.hpp"
#include "drape/glsl_func.hpp"
#include "drape/glsl_types.hpp"
#include <array>
#include <cstdint>
#include <string>
namespace dp
{
size_t constexpr kMaxBindingDecl = 8;
size_t constexpr kMaxBindingInfo = 3;
struct BindingDecl
{
std::string m_attributeName;
uint8_t m_componentCount;
glConst m_componentType;
uint8_t m_stride;
uint8_t m_offset;
bool operator==(BindingDecl const & other) const;
bool operator!=(BindingDecl const & other) const;
bool operator<(BindingDecl const & other) const;
};
class BindingInfo
{
public:
BindingInfo();
explicit BindingInfo(uint8_t count, uint8_t id = 0);
uint8_t GetCount() const;
uint8_t GetID() const;
BindingDecl const & GetBindingDecl(uint16_t index) const;
BindingDecl & GetBindingDecl(uint16_t index);
uint16_t GetElementSize() const;
bool IsDynamic() const;
bool operator==(BindingInfo const & other) const;
bool operator!=(BindingInfo const & other) const;
bool operator<(BindingInfo const & other) const;
protected:
std::array<BindingDecl, kMaxBindingDecl> m_bindings;
uint16_t m_info;
};
template <typename TFieldType, typename TVertexType>
uint8_t FillDecl(size_t index, std::string const & attrName, dp::BindingInfo & info, uint8_t offset)
{
dp::BindingDecl & decl = info.GetBindingDecl(static_cast<uint16_t>(index));
decl.m_attributeName = attrName;
decl.m_componentCount = glsl::GetComponentCount<TFieldType>();
decl.m_componentType = gl_const::GLFloatType;
decl.m_offset = offset;
decl.m_stride = sizeof(TVertexType);
return sizeof(TFieldType);
}
using BindingInfoArray = std::array<dp::BindingInfo, kMaxBindingInfo>;
template <typename TVertex>
class BindingFiller
{
public:
explicit BindingFiller(uint8_t count, uint8_t id = 0) : m_info(count, id) {}
template <typename TFieldType>
void FillDecl(std::string const & attrName)
{
m_offset += dp::FillDecl<TFieldType, TVertex>(m_index, attrName, m_info, m_offset);
++m_index;
}
dp::BindingInfo m_info;
private:
size_t m_index = 0;
uint8_t m_offset = 0;
};
} // namespace dp

View file

@ -0,0 +1,57 @@
#include "drape/buffer_base.hpp"
#include "base/assert.hpp"
namespace dp
{
BufferBase::BufferBase(uint8_t elementSize, uint32_t capacity)
: m_elementSize(elementSize)
, m_capacity(capacity)
, m_size(0)
{}
uint32_t BufferBase::GetCapacity() const
{
return m_capacity;
}
uint32_t BufferBase::GetCurrentSize() const
{
return m_size;
}
uint32_t BufferBase::GetAvailableSize() const
{
return m_capacity - m_size;
}
void BufferBase::Resize(uint32_t elementCount)
{
m_capacity = elementCount;
m_size = 0;
}
uint8_t BufferBase::GetElementSize() const
{
return m_elementSize;
}
void BufferBase::Seek(uint32_t elementNumber)
{
ASSERT(elementNumber <= m_capacity, ());
m_size = elementNumber;
}
void BufferBase::UploadData(uint32_t elementCount)
{
ASSERT(m_size + elementCount <= m_capacity, ());
m_size += elementCount;
}
void BufferBase::SetDataSize(uint32_t elementCount)
{
ASSERT(elementCount <= m_capacity, ());
m_size = elementCount;
}
} // namespace dp

View file

@ -0,0 +1,31 @@
#pragma once
#include <cstdint>
namespace dp
{
class BufferBase
{
public:
BufferBase(uint8_t elementSize, uint32_t capacity);
virtual ~BufferBase() = default;
uint32_t GetCapacity() const;
uint32_t GetCurrentSize() const;
uint32_t GetAvailableSize() const;
uint8_t GetElementSize() const;
virtual void Seek(uint32_t elementNumber);
protected:
void Resize(uint32_t elementCount);
void UploadData(uint32_t elementCount);
void SetDataSize(uint32_t elementCount);
private:
uint8_t m_elementSize;
uint32_t m_capacity;
uint32_t m_size;
};
} // namespace dp

86
libs/drape/color.hpp Normal file
View file

@ -0,0 +1,86 @@
#pragma once
#include "base/math.hpp"
#include <cstdint>
#include <sstream>
#include <string>
namespace dp
{
struct Color
{
constexpr Color() : Color(0, 0, 0, kMaxChannelValue) {}
constexpr explicit Color(uint32_t rgba) : m_rgba(rgba) {}
constexpr Color(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha)
: Color(red << 24 | green << 16 | blue << 8 | alpha)
{}
constexpr Color(uint8_t red, uint8_t green, uint8_t blue) : Color(red, green, blue, 255) {}
constexpr Color(uint32_t rgb, uint8_t alpha)
: Color(ExtractByte(rgb, 2), ExtractByte(rgb, 1), ExtractByte(rgb, 0), alpha)
{}
constexpr uint8_t GetRed() const { return ExtractByte(m_rgba, 3); }
constexpr uint8_t GetGreen() const { return ExtractByte(m_rgba, 2); }
constexpr uint8_t GetBlue() const { return ExtractByte(m_rgba, 1); }
constexpr uint8_t GetAlpha() const { return ExtractByte(m_rgba, 0); }
constexpr uint32_t GetRGBA() const { return m_rgba; }
constexpr uint32_t GetARGB() const
{
return (static_cast<uint32_t>(GetAlpha()) << 24) | (static_cast<uint32_t>(GetRed()) << 16) |
(static_cast<uint32_t>(GetGreen()) << 8) | static_cast<uint32_t>(GetBlue());
}
constexpr float GetRedF() const { return ChannelToFloat(GetRed()); }
constexpr float GetGreenF() const { return ChannelToFloat(GetGreen()); }
constexpr float GetBlueF() const { return ChannelToFloat(GetBlue()); }
constexpr float GetAlphaF() const { return ChannelToFloat(GetAlpha()); }
constexpr bool operator==(Color const & other) const { return m_rgba == other.m_rgba; }
constexpr bool operator!=(Color const & other) const { return m_rgba != other.m_rgba; }
constexpr bool operator<(Color const & other) const { return m_rgba < other.m_rgba; }
constexpr Color operator*(float s) const
{
return {static_cast<uint8_t>(math::Clamp(GetRedF() * s, 0.0f, 1.0f) * 255.0f),
static_cast<uint8_t>(math::Clamp(GetGreenF() * s, 0.0f, 1.0f) * 255.0f),
static_cast<uint8_t>(math::Clamp(GetBlueF() * s, 0.0f, 1.0f) * 255.0f), GetAlpha()};
}
constexpr static Color Black() { return {0, 0, 0, 255}; }
constexpr static Color White() { return {255, 255, 255, 255}; }
constexpr static Color Red() { return {255, 0, 0, 255}; }
constexpr static Color Blue() { return {0, 0, 255, 255}; }
constexpr static Color Green() { return {0, 255, 0, 255}; }
constexpr static Color Yellow() { return {255, 255, 0, 255}; }
constexpr static Color Transparent() { return {0, 0, 0, 0}; }
constexpr static Color FromARGB(uint32_t argb)
{
return {ExtractByte(argb, 2), ExtractByte(argb, 1), ExtractByte(argb, 0), ExtractByte(argb, 3)};
}
constexpr static Color FromRGBA(uint32_t rgba)
{
return {ExtractByte(rgba, 3), ExtractByte(rgba, 2), ExtractByte(rgba, 1), ExtractByte(rgba, 0)};
}
private:
constexpr static uint8_t ExtractByte(uint32_t number, uint8_t byteIdx) { return (number >> (8 * byteIdx)) & 0xFF; }
constexpr static float ChannelToFloat(uint8_t channelValue)
{
return static_cast<float>(channelValue) / kMaxChannelValue;
}
constexpr static uint8_t kMaxChannelValue = 255;
uint32_t m_rgba;
};
inline std::string DebugPrint(Color const & c)
{
std::ostringstream out;
out << "[R = " << static_cast<uint32_t>(c.GetRed()) << ", G = " << static_cast<uint32_t>(c.GetGreen())
<< ", B = " << static_cast<uint32_t>(c.GetBlue()) << ", A = " << static_cast<uint32_t>(c.GetAlpha()) << "]";
return out.str();
}
} // namespace dp

13
libs/drape/constants.hpp Normal file
View file

@ -0,0 +1,13 @@
#pragma once
#include "utils/projection.hpp"
namespace dp
{
namespace depth
{
float constexpr kMyPositionMarkDepth = kMaxDepth - 1.0f;
} // namespace depth
uint32_t constexpr kScreenPixelRectExtension = 75; // in pixels.
} // namespace dp

66
libs/drape/cpu_buffer.cpp Normal file
View file

@ -0,0 +1,66 @@
#include "drape/cpu_buffer.hpp"
#include "base/assert.hpp"
#include "base/math.hpp"
#include "base/shared_buffer_manager.hpp"
#include <cstring>
namespace dp
{
CPUBuffer::CPUBuffer(uint8_t elementSize, uint32_t capacity) : TBase(elementSize, capacity)
{
uint32_t memorySize = math::NextPowOf2(GetCapacity() * GetElementSize());
m_memory = SharedBufferManager::instance().reserveSharedBuffer(memorySize);
m_memoryCursor = NonConstData();
}
CPUBuffer::~CPUBuffer()
{
m_memoryCursor = nullptr;
SharedBufferManager::instance().freeSharedBuffer(m_memory->size(), m_memory);
}
void CPUBuffer::UploadData(void const * data, uint32_t elementCount)
{
CHECK(elementCount == 0 || data != nullptr, (elementCount));
uint32_t byteCountToCopy = GetElementSize() * elementCount;
#ifdef DEBUG
// Memory check
ASSERT(GetCursor() + byteCountToCopy <= Data() + m_memory->size(), ());
#endif
memcpy(GetCursor(), data, byteCountToCopy);
TBase::UploadData(elementCount);
}
void CPUBuffer::Seek(uint32_t elementNumber)
{
uint32_t offsetFromBegin = GetElementSize() * elementNumber;
ASSERT(Data() + offsetFromBegin <= Data() + m_memory->size(), ());
TBase::Seek(elementNumber);
m_memoryCursor = NonConstData() + offsetFromBegin;
}
uint32_t CPUBuffer::GetCurrentElementNumber() const
{
auto pointerDiff = static_cast<uint32_t>(GetCursor() - Data());
ASSERT(pointerDiff % GetElementSize() == 0, ());
return pointerDiff / GetElementSize();
}
unsigned char const * CPUBuffer::Data() const
{
return &((*m_memory)[0]);
}
unsigned char * CPUBuffer::NonConstData()
{
return &((*m_memory)[0]);
}
unsigned char * CPUBuffer::GetCursor() const
{
return m_memoryCursor;
}
} // namespace dp

33
libs/drape/cpu_buffer.hpp Normal file
View file

@ -0,0 +1,33 @@
#pragma once
#include "drape/buffer_base.hpp"
#include <memory>
#include <vector>
namespace dp
{
class CPUBuffer : public BufferBase
{
using TBase = BufferBase;
public:
CPUBuffer(uint8_t elementSize, uint32_t capacity);
~CPUBuffer() override;
void UploadData(void const * data, uint32_t elementCount);
// Set memory cursor on element with number == "elementNumber"
// Element numbers start from 0
void Seek(uint32_t elementNumber) override;
// Check function. In real world use must use it only in assert
uint32_t GetCurrentElementNumber() const;
unsigned char const * Data() const;
private:
unsigned char * NonConstData();
unsigned char * GetCursor() const;
unsigned char * m_memoryCursor;
std::shared_ptr<std::vector<unsigned char>> m_memory;
};
} // namespace dp

View file

@ -0,0 +1,76 @@
#include "drape/data_buffer.hpp"
#include "drape/data_buffer_impl.hpp"
#include "drape/drape_global.hpp"
namespace dp
{
DataBuffer::DataBuffer(uint8_t elementSize, uint32_t capacity)
: m_impl(make_unique_dp<CpuBufferImpl>(elementSize, capacity))
{}
ref_ptr<DataBufferBase> DataBuffer::GetBuffer() const
{
ASSERT(m_impl != nullptr, ());
return make_ref(m_impl);
}
void DataBuffer::MoveToGPU(ref_ptr<GraphicsContext> context, GPUBuffer::Target target, uint64_t batcherHash)
{
// If currentSize is 0 buffer hasn't been filled on preparation stage, let it be filled further.
uint32_t const currentSize = m_impl->GetCurrentSize();
auto const apiVersion = context->GetApiVersion();
if (apiVersion == dp::ApiVersion::OpenGLES3)
{
if (currentSize != 0)
{
m_impl =
make_unique_dp<GpuBufferImpl>(target, m_impl->Data(), m_impl->GetElementSize(), currentSize, batcherHash);
}
else
{
m_impl = make_unique_dp<GpuBufferImpl>(target, nullptr, m_impl->GetElementSize(), m_impl->GetAvailableSize(),
batcherHash);
}
}
else if (apiVersion == dp::ApiVersion::Metal)
{
#if defined(OMIM_METAL_AVAILABLE)
if (currentSize != 0)
m_impl = CreateImplForMetal(context, m_impl->Data(), m_impl->GetElementSize(), currentSize);
else
m_impl = CreateImplForMetal(context, nullptr, m_impl->GetElementSize(), m_impl->GetAvailableSize());
#endif
}
else if (apiVersion == dp::ApiVersion::Vulkan)
{
if (currentSize != 0)
m_impl = CreateImplForVulkan(context, m_impl->Data(), m_impl->GetElementSize(), currentSize, batcherHash);
else
m_impl = CreateImplForVulkan(context, nullptr, m_impl->GetElementSize(), m_impl->GetAvailableSize(), batcherHash);
}
else
{
CHECK(false, ("Unsupported API version."));
}
}
DataBufferMapper::DataBufferMapper(ref_ptr<GraphicsContext> context, ref_ptr<DataBuffer> buffer, uint32_t elementOffset,
uint32_t elementCount)
: m_context(context)
, m_buffer(buffer)
{
m_buffer->GetBuffer()->Bind();
m_ptr = m_buffer->GetBuffer()->Map(m_context, elementOffset, elementCount);
}
DataBufferMapper::~DataBufferMapper()
{
m_buffer->GetBuffer()->Unmap(m_context);
}
void DataBufferMapper::UpdateData(void const * data, uint32_t elementOffset, uint32_t elementCount)
{
m_buffer->GetBuffer()->UpdateData(m_ptr, data, elementOffset, elementCount);
}
} // namespace dp

View file

@ -0,0 +1,63 @@
#pragma once
#include "drape/gpu_buffer.hpp"
#include "drape/graphics_context.hpp"
#include "drape/pointers.hpp"
namespace dp
{
class DataBufferBase
{
public:
virtual ~DataBufferBase() = default;
virtual uint32_t GetCapacity() const = 0;
virtual uint32_t GetCurrentSize() const = 0;
virtual uint32_t GetAvailableSize() const = 0;
virtual uint8_t GetElementSize() const = 0;
virtual void Seek(uint32_t elementNumber) = 0;
virtual void const * Data() const = 0;
virtual void UploadData(ref_ptr<GraphicsContext> context, void const * data, uint32_t elementCount) = 0;
virtual void * Map(ref_ptr<GraphicsContext> context, uint32_t elementOffset, uint32_t elementCount) = 0;
virtual void UpdateData(void * destPtr, void const * srcPtr, uint32_t elementOffset, uint32_t elementCount) = 0;
virtual void Unmap(ref_ptr<GraphicsContext> context) = 0;
virtual void Bind() = 0;
};
class DataBuffer
{
public:
DataBuffer(uint8_t elementSize, uint32_t capacity);
ref_ptr<DataBufferBase> GetBuffer() const;
void MoveToGPU(ref_ptr<GraphicsContext> context, GPUBuffer::Target target, uint64_t batcherHash);
private:
// Definition of this method is in a .mm-file.
drape_ptr<DataBufferBase> CreateImplForMetal(ref_ptr<GraphicsContext> context, void const * data, uint8_t elementSize,
uint32_t capacity);
// Definition of this method is in a separate .cpp-file.
drape_ptr<DataBufferBase> CreateImplForVulkan(ref_ptr<GraphicsContext> context, void const * data,
uint8_t elementSize, uint32_t capacity, uint64_t batcherHash);
drape_ptr<DataBufferBase> m_impl;
};
class DataBufferMapper
{
public:
DataBufferMapper(ref_ptr<GraphicsContext> context, ref_ptr<DataBuffer> buffer, uint32_t elementOffset,
uint32_t elementCount);
~DataBufferMapper();
void UpdateData(void const * data, uint32_t elementOffset, uint32_t elementCount);
private:
ref_ptr<GraphicsContext> m_context;
ref_ptr<DataBuffer> m_buffer;
void * m_ptr;
};
} // namespace dp

View file

@ -0,0 +1,117 @@
#pragma once
#include "drape/cpu_buffer.hpp"
#include "drape/data_buffer.hpp"
#include "drape/gpu_buffer.hpp"
#include "base/macros.hpp"
#include <cstdint>
#include <utility>
namespace dp
{
// Generic implementation of data buffer.
template <typename TBuffer>
class DataBufferImpl : public DataBufferBase
{
public:
template <typename... Args>
DataBufferImpl(Args &&... params) : m_buffer(make_unique_dp<TBuffer>(std::forward<Args>(params)...))
{}
uint32_t GetCapacity() const override { return m_buffer->GetCapacity(); }
uint32_t GetCurrentSize() const override { return m_buffer->GetCurrentSize(); }
uint32_t GetAvailableSize() const override { return m_buffer->GetAvailableSize(); }
uint8_t GetElementSize() const override { return m_buffer->GetElementSize(); }
void Seek(uint32_t elementNumber) override { m_buffer->Seek(elementNumber); }
protected:
drape_ptr<TBuffer> m_buffer;
};
// CPU implementation of data buffer.
class CpuBufferImpl : public DataBufferImpl<CPUBuffer>
{
public:
template <typename... Args>
CpuBufferImpl(Args &&... params) : DataBufferImpl(std::forward<Args>(params)...)
{}
void const * Data() const override { return m_buffer->Data(); }
void UploadData(ref_ptr<GraphicsContext> context, void const * data, uint32_t elementCount) override
{
UNUSED_VALUE(context);
m_buffer->UploadData(data, elementCount);
uint32_t const newOffset = m_buffer->GetCurrentSize();
m_buffer->Seek(newOffset);
}
void * Map(ref_ptr<GraphicsContext> context, uint32_t elementOffset, uint32_t elementCount) override
{
UNUSED_VALUE(context);
UNUSED_VALUE(elementOffset);
UNUSED_VALUE(elementCount);
ASSERT(false, ("Mapping is unavailable for CPU buffer"));
return nullptr;
}
void UpdateData(void * destPtr, void const * srcPtr, uint32_t elementOffset, uint32_t elementCount) override
{
UNUSED_VALUE(destPtr);
UNUSED_VALUE(srcPtr);
UNUSED_VALUE(elementOffset);
UNUSED_VALUE(elementCount);
ASSERT(false, ("Data updating is unavailable for CPU buffer"));
}
void Unmap(ref_ptr<GraphicsContext> context) override
{
UNUSED_VALUE(context);
ASSERT(false, ("Unmapping is unavailable for CPU buffer"));
}
void Bind() override { ASSERT(false, ("Binding is unavailable for CPU buffer")); }
};
// GPU implementation of data buffer for OpenGL.
class GpuBufferImpl : public DataBufferImpl<GPUBuffer>
{
public:
template <typename... Args>
GpuBufferImpl(Args &&... params) : DataBufferImpl(std::forward<Args>(params)...)
{}
void const * Data() const override
{
ASSERT(false, ("Retrieving of raw data is unavailable for GPU buffer"));
return nullptr;
}
void UploadData(ref_ptr<GraphicsContext> context, void const * data, uint32_t elementCount) override
{
UNUSED_VALUE(context);
m_buffer->UploadData(data, elementCount);
}
void * Map(ref_ptr<GraphicsContext> context, uint32_t elementOffset, uint32_t elementCount) override
{
UNUSED_VALUE(context);
return m_buffer->Map(elementOffset, elementCount);
}
void UpdateData(void * destPtr, void const * srcPtr, uint32_t elementOffset, uint32_t elementCount) override
{
m_buffer->UpdateData(destPtr, srcPtr, elementOffset, elementCount);
}
void Unmap(ref_ptr<GraphicsContext> context) override
{
UNUSED_VALUE(context);
m_buffer->Unmap();
}
void Bind() override { m_buffer->Bind(); }
};
} // namespace dp

View file

@ -0,0 +1,20 @@
#pragma once
#include "drape/color.hpp"
#include "drape/overlay_tree.hpp"
namespace dp
{
class GraphicsContext;
class DebugRenderer
{
public:
virtual ~DebugRenderer() = default;
virtual bool IsEnabled() const = 0;
virtual void DrawRect(ref_ptr<GraphicsContext> context, ScreenBase const & screen, m2::RectF const & rect,
Color const & color) = 0;
virtual void DrawArrow(ref_ptr<GraphicsContext> context, ScreenBase const & screen,
OverlayTree::DisplacementData const & data) = 0;
};
} // namespace dp

View file

@ -0,0 +1,31 @@
#pragma once
// #define DRAW_TILE_NET
// #define DEBUG_OVERLAYS_OUTPUT
// #define CHECK_VBO_BOUNDS
// #define TRACK_POINTERS
// #define DEBUG_ANIMATIONS
// #define LINES_GENERATION_CALC_FILTERED_POINTS
// #define GPS_TRACK_SHOW_RAW_POINTS
// #define DEBUG_MESSAGE_QUEUE
// #define RENDER_DEBUG_INFO_LABELS
// #define DRAPE_MEASURER_BENCHMARK
// #define SCENARIO_ENABLE
// #define SHOW_FRAMES_STATS
#ifdef DRAPE_MEASURER_BENCHMARK
// #define RENDER_STATISTIC
// #define TILES_STATISTIC
// #define GENERATING_STATISTIC
// #define TRACK_GPU_MEM
// #define TRACK_GLYPH_USAGE
#endif
// #define ENABLE_VULKAN_DIAGNOSTICS
// #define ENABLE_VULKAN_DEBUG_DIAGNOSTICS_MESSAGES
// #define ENABLE_OPENGL_DIAGNOSTICS

142
libs/drape/drape_global.hpp Normal file
View file

@ -0,0 +1,142 @@
#pragma once
#include "drape/color.hpp"
#include "drape/pointers.hpp"
#include "geometry/point2d.hpp"
#include "base/assert.hpp"
#include "std/target_os.hpp"
#include <cstdint>
#include <functional>
#if defined(__APPLE__)
#define OMIM_METAL_AVAILABLE
#endif
namespace gpu
{
class ProgramManager;
} // namespace gpu
namespace dp
{
enum class ApiVersion
{
Invalid = -1,
OpenGLES3,
Metal,
Vulkan
};
/// @todo We have in code: if (anchor & dp::Center) which is not consistent with Center == 0.
/// Making Center == 1 breaks other defaults. Review this logic in future.
enum Anchor
{
Center = 0,
Left = 0x1,
Right = Left << 1,
Top = Right << 1,
Bottom = Top << 1,
LeftTop = Left | Top,
RightTop = Right | Top,
LeftBottom = Left | Bottom,
RightBottom = Right | Bottom
};
enum LineCap
{
SquareCap = -1,
RoundCap = 0,
ButtCap = 1,
};
enum LineJoin
{
MiterJoin = -1,
BevelJoin = 0,
RoundJoin = 1,
};
using DrapeID = uint64_t;
struct FontDecl
{
FontDecl() = default;
FontDecl(Color const & color, float size, Color const & outlineColor = Color::Transparent())
: m_color(color)
, m_outlineColor(outlineColor)
, m_size(size)
{}
Color m_color = Color::Transparent();
Color m_outlineColor = Color::Transparent();
float m_size = 0;
};
struct TitleDecl
{
dp::FontDecl m_primaryTextFont;
std::string m_primaryText;
dp::FontDecl m_secondaryTextFont;
std::string m_secondaryText;
dp::Anchor m_anchor = dp::Anchor::Center;
bool m_forceNoWrap = false;
m2::PointF m_primaryOffset = m2::PointF(0.0f, 0.0f);
m2::PointF m_secondaryOffset = m2::PointF(0.0f, 0.0f);
bool m_primaryOptional = false;
bool m_secondaryOptional = false;
};
class BaseFramebuffer
{
public:
virtual ~BaseFramebuffer() = default;
virtual void Bind() = 0;
};
inline std::string DebugPrint(dp::ApiVersion apiVersion)
{
switch (apiVersion)
{
case dp::ApiVersion::Invalid: return "Invalid";
case dp::ApiVersion::OpenGLES3: return "OpenGLES3";
case dp::ApiVersion::Metal: return "Metal";
case dp::ApiVersion::Vulkan: return "Vulkan";
}
return "Unknown";
}
inline dp::ApiVersion ApiVersionFromString(std::string const & str)
{
#if defined(OMIM_METAL_AVAILABLE)
if (str == "Metal")
return dp::ApiVersion::Metal;
#endif
#if defined(OMIM_OS_ANDROID)
if (str == "Vulkan")
return dp::ApiVersion::Vulkan;
#endif
if (str == "OpenGLES3")
return dp::ApiVersion::OpenGLES3;
// Default behavior for different OS. Appropriate fallback will be chosen
// if default API is not supported.
#if defined(OMIM_METAL_AVAILABLE)
return dp::ApiVersion::Metal;
#elif defined(OMIM_OS_ANDROID)
return dp::ApiVersion::Vulkan;
#else
return dp::ApiVersion::OpenGLES3;
#endif
}
class GraphicsContext;
class TextureManager;
using RenderInjectionHandler = std::function<void(ref_ptr<dp::GraphicsContext>, ref_ptr<TextureManager>,
ref_ptr<gpu::ProgramManager>, bool shutdown)>;
} // namespace dp

View file

@ -0,0 +1,218 @@
#pragma once
#include "base/assert.hpp"
#include "base/thread_pool_delayed.hpp"
#include <algorithm>
#include <atomic>
#include <condition_variable>
#include <memory>
#include <mutex>
#include <unordered_set>
#include <vector>
namespace dp
{
// This class MUST NOT run OpenGL-related tasks (which invoke OpenGL or contain any
// OpenGL data), use FR/BR threads for that.
class DrapeRoutine
{
public:
class Result
{
public:
void Wait()
{
if (m_isFinished)
return;
DrapeRoutine::Instance().Wait(m_id);
}
private:
friend class DrapeRoutine;
explicit Result(uint64_t id) : m_id(id), m_isFinished(false) {}
uint64_t Finish()
{
m_isFinished = true;
return m_id;
}
uint64_t const m_id;
std::atomic<bool> m_isFinished;
};
using ResultPtr = std::shared_ptr<Result>;
static void Init() { Instance(true /* reinitialize*/); }
static void Shutdown() { Instance().FinishAll(); }
template <typename Task>
static ResultPtr Run(Task && t)
{
ResultPtr result(new Result(Instance().GetNextId()));
auto const pushResult = Instance().m_workerThread.Push([result, t = std::forward<Task>(t)]() mutable
{
t();
Instance().Notify(result->Finish());
});
if (!pushResult.m_isSuccess)
return {};
return result;
}
template <typename Task>
static ResultPtr RunDelayed(base::DelayedThreadPool::Duration const & duration, Task && t)
{
ResultPtr result(new Result(Instance().GetNextId()));
auto const pushResult =
Instance().m_workerThread.PushDelayed(duration, [result, t = std::forward<Task>(t)]() mutable
{
t();
Instance().Notify(result->Finish());
});
if (!pushResult.m_isSuccess)
return {};
return result;
}
// Asynchronous execution for tasks when execution order matters.
template <typename Task>
static ResultPtr RunSequential(Task && t)
{
ResultPtr result(new Result(Instance().GetNextId()));
auto const pushResult = Instance().m_sequentialWorkerThread.Push([result, t = std::forward<Task>(t)]() mutable
{
t();
Instance().Notify(result->Finish());
});
if (!pushResult.m_isSuccess)
return {};
return result;
}
private:
static DrapeRoutine & Instance(bool reinitialize = false)
{
static std::unique_ptr<DrapeRoutine> instance;
if (!instance || reinitialize)
{
if (instance)
instance->FinishAll();
instance = std::unique_ptr<DrapeRoutine>(new DrapeRoutine());
}
return *instance;
}
DrapeRoutine() : m_workerThread(4 /* threads count */) {}
uint64_t GetNextId()
{
std::lock_guard<std::mutex> lock(m_mutex);
return m_counter++;
}
void Notify(uint64_t id)
{
std::lock_guard<std::mutex> lock(m_mutex);
m_finishedIds.insert(id);
m_condition.notify_all();
}
void Wait(uint64_t id)
{
std::unique_lock<std::mutex> lock(m_mutex);
if (m_finished)
return;
m_condition.wait(lock, [this, id]() { return m_finished || m_finishedIds.find(id) != m_finishedIds.end(); });
m_finishedIds.erase(id);
}
void FinishAll()
{
m_workerThread.ShutdownAndJoin();
m_sequentialWorkerThread.ShutdownAndJoin();
std::lock_guard<std::mutex> lock(m_mutex);
m_finished = true;
m_condition.notify_all();
}
std::unordered_set<uint64_t> m_finishedIds;
uint64_t m_counter = 0;
bool m_finished = false;
std::condition_variable m_condition;
std::mutex m_mutex;
base::DelayedThreadPool m_workerThread;
base::DelayedThreadPool m_sequentialWorkerThread;
};
// This is a helper class, which aggregates logic of waiting for active
// tasks completion. It must be used when we provide tasks completion
// before subsystem shutting down.
template <typename TaskType>
class ActiveTasks
{
struct ActiveTask
{
std::shared_ptr<TaskType> m_task;
DrapeRoutine::ResultPtr m_result;
ActiveTask(std::shared_ptr<TaskType> && task, DrapeRoutine::ResultPtr && result)
: m_task(std::move(task))
, m_result(std::move(result))
{}
};
public:
~ActiveTasks() { FinishAll(); }
void Add(std::shared_ptr<TaskType> && task, DrapeRoutine::ResultPtr && result)
{
ASSERT(task != nullptr, ());
ASSERT(result != nullptr, ());
std::lock_guard<std::mutex> lock(m_mutex);
m_tasks.emplace_back(std::move(task), std::move(result));
}
void Remove(std::shared_ptr<TaskType> const & task)
{
std::lock_guard<std::mutex> lock(m_mutex);
m_tasks.erase(
std::remove_if(m_tasks.begin(), m_tasks.end(), [task](ActiveTask const & t) { return t.m_task == task; }),
m_tasks.end());
}
void FinishAll()
{
// Move tasks to a temporary vector, because m_tasks
// can be modified during 'Cancel' calls.
std::vector<ActiveTask> tasks;
{
std::lock_guard<std::mutex> lock(m_mutex);
tasks.swap(m_tasks);
}
// Cancel all tasks.
for (auto & t : tasks)
t.m_task->Cancel();
// Wait for completion of unfinished tasks.
for (auto & t : tasks)
t.m_result->Wait();
}
private:
std::vector<ActiveTask> m_tasks;
std::mutex m_mutex;
};
} // namespace dp

View file

@ -0,0 +1,51 @@
project(drape_tests)
set(DRAPE_TESTABLE_SRC_PREFIXED ${DRAPE_TESTABLE_SRC})
list(TRANSFORM DRAPE_TESTABLE_SRC_PREFIXED PREPEND "../")
set(SRC
${DRAPE_TESTABLE_SRC_PREFIXED}
attribute_provides_tests.cpp
batcher_tests.cpp
bingind_info_tests.cpp
buffer_tests.cpp
dummy_texture.hpp
font_texture_tests.cpp
gl_functions.cpp
gl_mock_functions.cpp
gl_mock_functions.hpp
glyph_mng_tests.cpp
glyph_packer_test.cpp
harfbuzz_shaping_test.cpp
img.cpp
img.hpp
memory_comparer.hpp
object_pool_tests.cpp
pointers_tests.cpp
static_texture_tests.cpp
stipple_pen_tests.cpp
texture_of_colors_tests.cpp
testing_graphics_context.hpp
uniform_value_tests.cpp
vertex_buffer_tests.cpp
)
omim_add_test(${PROJECT_NAME} ${SRC})
target_compile_definitions(${PROJECT_NAME}
PRIVATE GTEST_DONT_DEFINE_TEST WITH_GL_MOCK
)
if (WITH_SYSTEM_PROVIDED_3PARTY)
find_package(GTest REQUIRED)
target_link_libraries(${PROJECT_NAME} GTest::gtest)
else()
add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/../../../3party/googletest" "${CMAKE_CURRENT_BINARY_DIR}/googletest")
target_include_directories(${PROJECT_NAME} PUBLIC ${OMIM_ROOT}/3party/glm)
endif()
target_link_libraries(${PROJECT_NAME}
qt_tstfrm
gmock
${DRAPE_LINK_LIBRARIES}
)

View file

@ -0,0 +1,201 @@
#include "testing/testing.hpp"
#include "drape/attribute_provider.hpp"
#include <gmock/gmock.h>
using namespace dp;
UNIT_TEST(InitStreamsTest)
{
int const VERTEX_COUNT = 10;
AttributeProvider provider(3, VERTEX_COUNT);
float positions[2 * VERTEX_COUNT];
float depth[VERTEX_COUNT];
float normals[2 * VERTEX_COUNT];
for (int i = 0; i < VERTEX_COUNT; ++i)
{
positions[2 * i] = (float)i;
positions[(2 * i) + 1] = 0.0f;
depth[i] = (float)i;
normals[2 * i] = (float)i;
normals[(2 * i) + 1] = 0.0;
}
{
BindingInfo zeroStreamBinding(1);
BindingDecl & decl = zeroStreamBinding.GetBindingDecl(0);
decl.m_attributeName = "position";
decl.m_componentCount = 2;
decl.m_componentType = gl_const::GLFloatType;
decl.m_offset = 0;
decl.m_stride = 0;
provider.InitStream(0, zeroStreamBinding, make_ref(positions));
}
{
BindingInfo firstStreamBinding(1);
BindingDecl & decl = firstStreamBinding.GetBindingDecl(0);
decl.m_attributeName = "depth";
decl.m_componentCount = 1;
decl.m_componentType = gl_const::GLFloatType;
decl.m_offset = 0;
decl.m_stride = 0;
provider.InitStream(1, firstStreamBinding, make_ref(depth));
}
{
BindingInfo secondStreamBinding(1);
BindingDecl & decl = secondStreamBinding.GetBindingDecl(0);
decl.m_attributeName = "normal";
decl.m_componentCount = 2;
decl.m_componentType = gl_const::GLFloatType;
decl.m_offset = 0;
decl.m_stride = 0;
provider.InitStream(2, secondStreamBinding, make_ref(normals));
}
TEST_EQUAL(provider.IsDataExists(), true, ());
TEST_EQUAL(provider.GetVertexCount(), 10, ());
TEST_EQUAL(provider.GetRawPointer(0), (void *)positions, ());
TEST_EQUAL(provider.GetRawPointer(1), (void *)depth, ());
TEST_EQUAL(provider.GetRawPointer(2), (void *)normals, ());
provider.Advance(1);
TEST_EQUAL(provider.IsDataExists(), true, ());
TEST_EQUAL(provider.GetVertexCount(), 9, ());
TEST_EQUAL(provider.GetRawPointer(0), (void *)(&positions[2]), ());
TEST_EQUAL(provider.GetRawPointer(1), (void *)(&depth[1]), ());
TEST_EQUAL(provider.GetRawPointer(2), (void *)(&normals[2]), ());
provider.Advance(9);
TEST_EQUAL(provider.IsDataExists(), false, ());
TEST_EQUAL(provider.GetVertexCount(), 0, ());
}
UNIT_TEST(InterleavedStreamTest)
{
int const VERTEX_COUNT = 10;
AttributeProvider provider(1, 10);
float data[5 * VERTEX_COUNT];
for (int i = 0; i < VERTEX_COUNT; ++i)
{
data[(5 * i)] = (float)i;
data[(5 * i) + 1] = 0.0;
data[(5 * i) + 2] = (float)i;
data[(5 * i) + 3] = (float)i;
data[(5 * i) + 4] = 0.0;
}
BindingInfo binding(3);
{
BindingDecl & decl = binding.GetBindingDecl(0);
decl.m_attributeName = "position";
decl.m_componentCount = 2;
decl.m_componentType = gl_const::GLFloatType;
decl.m_offset = 0;
decl.m_stride = 5 * sizeof(float);
}
{
BindingDecl & decl = binding.GetBindingDecl(1);
decl.m_attributeName = "depth";
decl.m_componentCount = 1;
decl.m_componentType = gl_const::GLFloatType;
decl.m_offset = 2 * sizeof(float);
decl.m_stride = 5 * sizeof(float);
}
{
BindingDecl & decl = binding.GetBindingDecl(2);
decl.m_attributeName = "normal";
decl.m_componentCount = 2;
decl.m_componentType = gl_const::GLFloatType;
decl.m_offset = 3 * sizeof(float);
decl.m_stride = 5 * sizeof(float);
}
provider.InitStream(0, binding, make_ref(data));
TEST_EQUAL(provider.IsDataExists(), true, ());
TEST_EQUAL(provider.GetVertexCount(), 10, ());
TEST_EQUAL(provider.GetRawPointer(0), (void *)data, ());
provider.Advance(1);
TEST_EQUAL(provider.IsDataExists(), true, ());
TEST_EQUAL(provider.GetVertexCount(), 9, ());
TEST_EQUAL(provider.GetRawPointer(0), (void *)(&data[5]), ());
provider.Advance(9);
TEST_EQUAL(provider.IsDataExists(), false, ());
TEST_EQUAL(provider.GetVertexCount(), 0, ());
}
UNIT_TEST(MixedStreamsTest)
{
int const VERTEX_COUNT = 10;
AttributeProvider provider(2, 10);
float position[3 * VERTEX_COUNT];
float normal[2 * VERTEX_COUNT];
for (int i = 0; i < VERTEX_COUNT; ++i)
{
position[3 * i] = (float)i; // x
position[(3 * i) + 1] = 0.0; // y
position[(3 * i) + 2] = (float)i; // z
position[2 * i] = (float)i; // Nx
position[(2 * i) + 1] = 0.0; // Ny
}
{
BindingInfo binding(2);
{
BindingDecl & decl = binding.GetBindingDecl(0);
decl.m_attributeName = "position";
decl.m_componentCount = 2;
decl.m_componentType = gl_const::GLFloatType;
decl.m_offset = 0;
decl.m_stride = 3 * sizeof(float);
}
{
BindingDecl & decl = binding.GetBindingDecl(1);
decl.m_attributeName = "depth";
decl.m_componentCount = 1;
decl.m_componentType = gl_const::GLFloatType;
decl.m_offset = 2 * sizeof(float);
decl.m_stride = 3 * sizeof(float);
}
provider.InitStream(0, binding, make_ref(position));
}
{
BindingInfo binding(1);
BindingDecl & decl = binding.GetBindingDecl(0);
decl.m_attributeName = "normal";
decl.m_componentCount = 2;
decl.m_componentType = gl_const::GLFloatType;
decl.m_offset = 0;
decl.m_stride = 0;
provider.InitStream(1, binding, make_ref(normal));
}
TEST_EQUAL(provider.IsDataExists(), true, ());
TEST_EQUAL(provider.GetVertexCount(), 10, ());
TEST_EQUAL(provider.GetRawPointer(0), (void *)position, ());
TEST_EQUAL(provider.GetRawPointer(1), (void *)normal, ());
provider.Advance(1);
TEST_EQUAL(provider.IsDataExists(), true, ());
TEST_EQUAL(provider.GetVertexCount(), 9, ());
TEST_EQUAL(provider.GetRawPointer(0), (void *)(&position[3]), ());
TEST_EQUAL(provider.GetRawPointer(1), (void *)(&normal[2]), ());
provider.Advance(9);
TEST_EQUAL(provider.IsDataExists(), false, ());
TEST_EQUAL(provider.GetVertexCount(), 0, ());
}

View file

@ -0,0 +1,355 @@
#include "testing/testing.hpp"
#include "drape/drape_tests/gl_mock_functions.hpp"
#include "drape/drape_tests/memory_comparer.hpp"
#include "drape/drape_tests/testing_graphics_context.hpp"
#include "drape/batcher.hpp"
#include "drape/gl_constants.hpp"
#include "drape/index_storage.hpp"
#include "drape/vertex_array_buffer.hpp"
#include "base/stl_helpers.hpp"
#include <cstring>
#include <functional>
#include <vector>
#include <gmock/gmock.h>
using testing::_;
using testing::AnyOf;
using testing::IgnoreResult;
using testing::InSequence;
using testing::Invoke;
using testing::Return;
using namespace dp;
using namespace std::placeholders;
namespace
{
struct VAOAcceptor
{
virtual void FlushFullBucket(RenderState const & /* state */, drape_ptr<RenderBucket> && bucket)
{
m_vao.push_back(std::move(bucket));
}
std::vector<drape_ptr<RenderBucket>> m_vao;
};
class TestExtension : public dp::BaseRenderStateExtension
{
public:
bool Less(ref_ptr<dp::BaseRenderStateExtension> other) const override { return false; }
bool Equal(ref_ptr<dp::BaseRenderStateExtension> other) const override { return true; }
};
class BatcherExpectations
{
public:
BatcherExpectations() : m_indexBufferID(1), m_dataBufferID(2) {}
template <typename TBatcherCall>
void RunTest(float * vertexes, void * indexes, uint32_t vertexCount, uint8_t vertexComponentCount,
uint32_t indexCount, TBatcherCall const & fn)
{
uint32_t const vertexSize = vertexCount * vertexComponentCount;
MemoryComparer const dataCmp(vertexes, vertexSize * sizeof(float));
MemoryComparer const indexCmp(indexes, indexCount * dp::IndexStorage::SizeOfIndex());
ExpectBufferCreation(vertexSize, indexCount, indexCmp, dataCmp);
auto renderState = make_unique_dp<TestExtension>();
auto state = RenderState(0, make_ref(renderState));
BindingInfo binding(1);
BindingDecl & decl = binding.GetBindingDecl(0);
decl.m_attributeName = "position";
decl.m_componentCount = vertexComponentCount;
decl.m_componentType = gl_const::GLFloatType;
decl.m_offset = 0;
decl.m_stride = 0;
AttributeProvider provider(1, vertexCount);
provider.InitStream(0, binding, make_ref(vertexes));
TestingGraphicsContext context;
VAOAcceptor vaoAcceptor;
Batcher batcher(65000, 65000);
batcher.StartSession(std::bind(&VAOAcceptor::FlushFullBucket, &vaoAcceptor, _1, _2));
fn(&batcher, state, make_ref(&provider));
batcher.EndSession(make_ref(&context));
ExpectBufferDeletion();
for (size_t i = 0; i < vaoAcceptor.m_vao.size(); ++i)
vaoAcceptor.m_vao[i].reset();
}
void ExpectBufferCreation(uint32_t vertexCount, uint32_t indexCount, MemoryComparer const & indexCmp,
MemoryComparer const & vertexCmp)
{
InSequence seq;
// data buffer creation
EXPECTGL(glGenBuffer()).WillOnce(Return(m_dataBufferID));
EXPECTGL(glBindBuffer(m_dataBufferID, gl_const::GLArrayBuffer));
EXPECTGL(glBufferData(gl_const::GLArrayBuffer, vertexCount * sizeof(float), _, gl_const::GLDynamicDraw))
.WillOnce(Invoke(&vertexCmp, &MemoryComparer::cmpSubBuffer));
// Index buffer creation
EXPECTGL(glGenBuffer()).WillOnce(Return(m_indexBufferID));
EXPECTGL(glBindBuffer(m_indexBufferID, gl_const::GLElementArrayBuffer));
EXPECTGL(glBufferData(gl_const::GLElementArrayBuffer, indexCount * dp::IndexStorage::SizeOfIndex(), _,
gl_const::GLDynamicDraw))
.WillOnce(Invoke(&indexCmp, &MemoryComparer::cmpSubBuffer));
EXPECTGL(glBindBuffer(0, gl_const::GLElementArrayBuffer));
EXPECTGL(glBindBuffer(0, gl_const::GLArrayBuffer));
}
void ExpectBufferDeletion()
{
InSequence seq;
EXPECTGL(glBindBuffer(0, gl_const::GLElementArrayBuffer));
EXPECTGL(glDeleteBuffer(m_indexBufferID));
EXPECTGL(glBindBuffer(0, gl_const::GLArrayBuffer));
EXPECTGL(glDeleteBuffer(m_dataBufferID));
}
private:
uint32_t m_indexBufferID = 0;
uint32_t m_dataBufferID = 0;
};
} // namespace
UNIT_TEST(BatchLists_Test)
{
TestingGraphicsContext context;
uint32_t const kVerticesCount = 12;
uint32_t const kFloatsCount = 3 * 12; // 3 component on each vertex.
float data[kFloatsCount];
for (uint32_t i = 0; i < kFloatsCount; ++i)
data[i] = static_cast<float>(i);
std::vector<uint32_t> indexesRaw(kVerticesCount);
for (uint32_t i = 0; i < kVerticesCount; ++i)
indexesRaw[i] = i;
dp::IndexStorage indexes(std::move(indexesRaw));
BatcherExpectations expectations;
auto fn = [&context](Batcher * batcher, RenderState const & state, ref_ptr<AttributeProvider> p)
{ batcher->InsertTriangleList(make_ref(&context), state, p); };
expectations.RunTest(data, indexes.GetRaw(), kVerticesCount, 3, kVerticesCount, fn);
}
UNIT_TEST(BatchListOfStript_4stride)
{
TestingGraphicsContext context;
uint32_t const kVerticesCount = 12;
uint32_t const kIndicesCount = 18;
float data[3 * kVerticesCount];
for (uint32_t i = 0; i < kVerticesCount * 3; ++i)
data[i] = static_cast<float>(i);
std::vector<uint32_t> indexesRaw = {0, 1, 2, 1, 3, 2, 4, 5, 6, 5, 7, 6, 8, 9, 10, 9, 11, 10};
dp::IndexStorage indexes(std::move(indexesRaw));
BatcherExpectations expectations;
auto fn = [&context](Batcher * batcher, RenderState const & state, ref_ptr<AttributeProvider> p)
{ batcher->InsertListOfStrip(make_ref(&context), state, p, dp::Batcher::VertexPerQuad); };
expectations.RunTest(data, indexes.GetRaw(), kVerticesCount, 3, kIndicesCount, fn);
}
UNIT_TEST(BatchListOfStript_5stride)
{
TestingGraphicsContext context;
uint32_t const kVerticesCount = 15;
uint32_t const kIndicesCount = 27;
float data[3 * kVerticesCount];
for (uint32_t i = 0; i < kVerticesCount * 3; ++i)
data[i] = static_cast<float>(i);
std::vector<uint32_t> indexesRaw = {0, 1, 2, 1, 3, 2, 2, 3, 4, 5, 6, 7, 6, 8,
7, 7, 8, 9, 10, 11, 12, 11, 13, 12, 12, 13, 14};
dp::IndexStorage indexes(std::move(indexesRaw));
BatcherExpectations expectations;
auto fn = [&context](Batcher * batcher, RenderState const & state, ref_ptr<AttributeProvider> p)
{ batcher->InsertListOfStrip(make_ref(&context), state, p, 5); };
expectations.RunTest(data, indexes.GetRaw(), kVerticesCount, 3, kIndicesCount, fn);
}
UNIT_TEST(BatchListOfStript_6stride)
{
TestingGraphicsContext context;
uint32_t const kVerticesCount = 18;
uint32_t const kIndicesCount = 36;
float data[3 * kVerticesCount];
for (uint32_t i = 0; i < kVerticesCount * 3; ++i)
data[i] = static_cast<float>(i);
std::vector<uint32_t> indexesRaw = {0, 1, 2, 1, 3, 2, 2, 3, 4, 3, 5, 4, 6, 7, 8, 7, 9, 8,
8, 9, 10, 9, 11, 10, 12, 13, 14, 13, 15, 14, 14, 15, 16, 15, 17, 16};
dp::IndexStorage indexes(std::move(indexesRaw));
BatcherExpectations expectations;
auto fn = [&context](Batcher * batcher, RenderState const & state, ref_ptr<AttributeProvider> p)
{ batcher->InsertListOfStrip(make_ref(&context), state, p, 6); };
expectations.RunTest(data, indexes.GetRaw(), kVerticesCount, 3, kIndicesCount, fn);
}
namespace
{
class PartialBatcherTest
{
public:
struct BufferNode
{
BufferNode(uint32_t indexByteCount, uint32_t vertexByteCount, void * indexData, void * vertexData)
: m_indexByteCount(indexByteCount)
, m_vertexByteCount(vertexByteCount)
, m_indexData(indexData)
, m_vertexData(vertexData)
, m_indexBufferID(0)
, m_vertexBufferID(0)
{}
uint32_t m_indexByteCount;
uint32_t m_vertexByteCount;
void * m_indexData;
void * m_vertexData;
uint32_t m_indexBufferID;
uint32_t m_vertexBufferID;
};
PartialBatcherTest() = default;
~PartialBatcherTest() { std::for_each(m_comparators.begin(), m_comparators.end(), base::DeleteFunctor()); }
void AddBufferNode(BufferNode const & node)
{
m_nodes.push_back(node);
BufferNode & currentNode = m_nodes.back();
currentNode.m_indexBufferID = m_bufferIDCounter++;
currentNode.m_vertexBufferID = m_bufferIDCounter++;
// data buffer creation
EXPECTGL(glGenBuffer()).WillOnce(Return(currentNode.m_vertexBufferID));
EXPECTGL(glBindBuffer(currentNode.m_vertexBufferID, gl_const::GLArrayBuffer));
m_comparators.push_back(new MemoryComparer(currentNode.m_vertexData, currentNode.m_vertexByteCount));
MemoryComparer * vertexComparer = m_comparators.back();
EXPECTGL(glBufferData(gl_const::GLArrayBuffer, currentNode.m_vertexByteCount, _, gl_const::GLDynamicDraw))
.WillOnce(Invoke(vertexComparer, &MemoryComparer::cmpSubBuffer));
// Index buffer creation
EXPECTGL(glGenBuffer()).WillOnce(Return(currentNode.m_indexBufferID));
EXPECTGL(glBindBuffer(currentNode.m_indexBufferID, gl_const::GLElementArrayBuffer));
m_comparators.push_back(new MemoryComparer(currentNode.m_indexData, currentNode.m_indexByteCount));
MemoryComparer * indexComparer = m_comparators.back();
EXPECTGL(glBufferData(gl_const::GLElementArrayBuffer, currentNode.m_indexByteCount, _, gl_const::GLDynamicDraw))
.WillOnce(Invoke(indexComparer, &MemoryComparer::cmpSubBuffer));
EXPECTGL(glBindBuffer(0, gl_const::GLElementArrayBuffer));
EXPECTGL(glBindBuffer(0, gl_const::GLArrayBuffer));
}
void CloseExpection()
{
for (auto const & node : m_nodes)
{
EXPECTGL(glBindBuffer(0, gl_const::GLElementArrayBuffer));
EXPECTGL(glDeleteBuffer(node.m_indexBufferID));
EXPECTGL(glBindBuffer(0, gl_const::GLArrayBuffer));
EXPECTGL(glDeleteBuffer(node.m_vertexBufferID));
}
}
private:
uint32_t m_bufferIDCounter = 1;
std::vector<BufferNode> m_nodes;
std::vector<MemoryComparer *> m_comparators;
};
} // namespace
UNIT_TEST(BatchListOfStript_partial)
{
TestingGraphicsContext context;
uint32_t const VertexCount = 16;
uint32_t const ComponentCount = 3;
uint32_t const VertexArraySize = VertexCount * ComponentCount;
uint32_t const IndexCount = 24;
uint32_t const FirstBufferVertexPortion = 12;
uint32_t const SecondBufferVertexPortion = VertexCount - FirstBufferVertexPortion;
uint32_t const FirstBufferIndexPortion = 18;
uint32_t const SecondBufferIndexPortion = IndexCount - FirstBufferIndexPortion;
float vertexData[VertexArraySize];
for (uint32_t i = 0; i < VertexArraySize; ++i)
vertexData[i] = static_cast<float>(i);
std::vector<uint32_t> indexDataRaw = {0, 1, 2, 1, 3, 2, 4, 5, 6, 5, 7,
6, 8, 9, 10, 9, 11, 10, 0, 1, 2, // start new buffer
1, 3, 2};
dp::IndexStorage indexData(std::move(indexDataRaw));
PartialBatcherTest::BufferNode node1(FirstBufferIndexPortion * dp::IndexStorage::SizeOfIndex(),
FirstBufferVertexPortion * ComponentCount * sizeof(float), indexData.GetRaw(),
vertexData);
PartialBatcherTest::BufferNode node2(SecondBufferIndexPortion * dp::IndexStorage::SizeOfIndex(),
SecondBufferVertexPortion * ComponentCount * sizeof(float),
indexData.GetRaw(FirstBufferIndexPortion),
vertexData + FirstBufferVertexPortion * ComponentCount);
using IndexVertexCount = std::pair<uint32_t, uint32_t>;
std::vector<IndexVertexCount> srcData;
srcData.emplace_back(30, 12);
srcData.emplace_back(30, 13);
srcData.emplace_back(18, 30);
srcData.emplace_back(19, 30);
for (size_t i = 0; i < srcData.size(); ++i)
{
InSequence seq;
PartialBatcherTest test;
test.AddBufferNode(node1);
test.AddBufferNode(node2);
test.CloseExpection();
auto renderState = make_unique_dp<TestExtension>();
auto state = RenderState(0, make_ref(renderState));
BindingInfo binding(1);
BindingDecl & decl = binding.GetBindingDecl(0);
decl.m_attributeName = "position";
decl.m_componentCount = ComponentCount;
decl.m_componentType = gl_const::GLFloatType;
decl.m_offset = 0;
decl.m_stride = 0;
AttributeProvider provider(1, VertexCount);
provider.InitStream(0, binding, make_ref(vertexData));
VAOAcceptor vaoAcceptor;
Batcher batcher(srcData[i].first, srcData[i].second);
batcher.StartSession(std::bind(&VAOAcceptor::FlushFullBucket, &vaoAcceptor, _1, _2));
batcher.InsertListOfStrip(make_ref(&context), state, make_ref(&provider), 4);
batcher.EndSession(make_ref(&context));
for (size_t i = 0; i < vaoAcceptor.m_vao.size(); ++i)
vaoAcceptor.m_vao[i].reset();
}
}

View file

@ -0,0 +1,31 @@
#include "testing/testing.hpp"
#include "drape/binding_info.hpp"
using namespace dp;
UNIT_TEST(BindingInfoIDTest)
{
{
BindingInfo info(1, 1);
TEST_EQUAL(info.GetID(), 1, ());
}
{
BindingInfo info(1);
TEST_EQUAL(info.GetID(), 0, ());
}
}
UNIT_TEST(DynamicHandlingTest)
{
{
BindingInfo info(1);
TEST_EQUAL(info.IsDynamic(), false, ());
}
{
BindingInfo info(1, 1);
TEST_EQUAL(info.IsDynamic(), true, ());
}
}

View file

@ -0,0 +1,111 @@
#include "testing/testing.hpp"
#include "drape/drape_tests/gl_mock_functions.hpp"
#include "drape/drape_tests/testing_graphics_context.hpp"
#include "drape/data_buffer.hpp"
#include "drape/gpu_buffer.hpp"
#include "drape/index_buffer.hpp"
#include "drape/index_storage.hpp"
#include <cstdlib>
#include <memory>
#include <gmock/gmock.h>
using namespace emul;
using namespace dp;
using ::testing::_;
using ::testing::InSequence;
using ::testing::Return;
UNIT_TEST(CreateDestroyDataBufferTest)
{
TestingGraphicsContext context;
InSequence s;
EXPECTGL(glGenBuffer()).WillOnce(Return(1));
EXPECTGL(glBindBuffer(1, gl_const::GLArrayBuffer));
EXPECTGL(glBufferData(gl_const::GLArrayBuffer, 3 * 100 * sizeof(float), nullptr, gl_const::GLDynamicDraw));
EXPECTGL(glBindBuffer(0, gl_const::GLArrayBuffer));
EXPECTGL(glDeleteBuffer(1));
std::unique_ptr<DataBuffer> buffer(new DataBuffer(3 * sizeof(float), 100));
buffer->MoveToGPU(make_ref(&context), GPUBuffer::ElementBuffer, 0);
}
UNIT_TEST(CreateDestroyIndexBufferTest)
{
TestingGraphicsContext context;
InSequence s;
EXPECTGL(glGenBuffer()).WillOnce(Return(1));
EXPECTGL(glBindBuffer(1, gl_const::GLElementArrayBuffer));
EXPECTGL(glBufferData(gl_const::GLElementArrayBuffer, 100 * dp::IndexStorage::SizeOfIndex(), nullptr,
gl_const::GLDynamicDraw));
EXPECTGL(glBindBuffer(0, gl_const::GLElementArrayBuffer));
EXPECTGL(glDeleteBuffer(1));
std::unique_ptr<IndexBuffer> buffer(new IndexBuffer(100));
buffer->MoveToGPU(make_ref(&context), GPUBuffer::IndexBuffer, 0);
}
UNIT_TEST(UploadDataTest)
{
float data[3 * 100];
for (int i = 0; i < 3 * 100; ++i)
data[i] = (float)i;
std::unique_ptr<DataBuffer> buffer(new DataBuffer(3 * sizeof(float), 100));
TestingGraphicsContext context;
InSequence s;
EXPECTGL(glGenBuffer()).WillOnce(Return(1));
EXPECTGL(glBindBuffer(1, gl_const::GLArrayBuffer));
EXPECTGL(glBufferData(gl_const::GLArrayBuffer, 3 * 100 * sizeof(float), buffer->GetBuffer()->Data(),
gl_const::GLDynamicDraw));
EXPECTGL(glBindBuffer(0, gl_const::GLArrayBuffer));
EXPECTGL(glDeleteBuffer(1));
buffer->GetBuffer()->UploadData(make_ref(&context), data, 100);
buffer->MoveToGPU(make_ref(&context), GPUBuffer::ElementBuffer, 0);
}
UNIT_TEST(ParticalUploadDataTest)
{
size_t const kPart1Size = 3 * 30;
float part1Data[kPart1Size];
for (size_t i = 0; i < kPart1Size; ++i)
part1Data[i] = (float)i;
size_t const kPart2Size = 3 * 100;
float part2Data[kPart2Size];
for (size_t i = 0; i < kPart2Size; ++i)
part2Data[i] = (float)i;
std::unique_ptr<DataBuffer> buffer(new DataBuffer(3 * sizeof(float), 100));
TestingGraphicsContext context;
InSequence s;
EXPECTGL(glGenBuffer()).WillOnce(Return(1));
EXPECTGL(glBindBuffer(1, gl_const::GLArrayBuffer));
EXPECTGL(glBufferData(gl_const::GLArrayBuffer, 3 * 100 * sizeof(float), buffer->GetBuffer()->Data(),
gl_const::GLDynamicDraw));
EXPECTGL(glBindBuffer(0, gl_const::GLArrayBuffer));
EXPECTGL(glDeleteBuffer(1));
TEST_EQUAL(buffer->GetBuffer()->GetCapacity(), 100, ());
TEST_EQUAL(buffer->GetBuffer()->GetAvailableSize(), 100, ());
TEST_EQUAL(buffer->GetBuffer()->GetCurrentSize(), 0, ());
buffer->GetBuffer()->UploadData(make_ref(&context), part1Data, 30);
TEST_EQUAL(buffer->GetBuffer()->GetCapacity(), 100, ());
TEST_EQUAL(buffer->GetBuffer()->GetAvailableSize(), 70, ());
TEST_EQUAL(buffer->GetBuffer()->GetCurrentSize(), 30, ());
buffer->GetBuffer()->UploadData(make_ref(&context), part2Data, 70);
TEST_EQUAL(buffer->GetBuffer()->GetCapacity(), 100, ());
TEST_EQUAL(buffer->GetBuffer()->GetAvailableSize(), 0, ());
TEST_EQUAL(buffer->GetBuffer()->GetCurrentSize(), 100, ());
buffer->MoveToGPU(make_ref(&context), GPUBuffer::ElementBuffer, 0);
}

View file

@ -0,0 +1,16 @@
#pragma once
#include "drape/pointers.hpp"
#include "drape/texture.hpp"
class DummyTexture : public dp::Texture
{
public:
ref_ptr<ResourceInfo> FindResource(Key const & key)
{
bool dummy = false;
return FindResource(key, dummy);
}
virtual ref_ptr<ResourceInfo> FindResource(Key const & /*key*/, bool & /*newResource*/) { return nullptr; }
};

View file

@ -0,0 +1,137 @@
/* This test crashes with ASSERT_NOT_EQUAL(CurrentApiVersion, dp::ApiVersion::Invalid, ()); in gl_functions.cpp
#include "drape/drape_tests/dummy_texture.hpp"
#include "drape/drape_tests/gl_mock_functions.hpp"
#include "drape/drape_tests/img.hpp"
#include "drape/drape_tests/testing_graphics_context.hpp"
#include "base/file_name_utils.hpp"
#include "platform/platform.hpp"
#include "qt_tstfrm/test_main_loop.hpp"
#include "testing/testing.hpp"
#include "drape/drape_routine.hpp"
#include "drape/font_constants.hpp"
#include "drape/font_texture.hpp"
#include "drape/glyph_manager.hpp"
#include <functional>
#include <QtCore/QPoint>
#include <QtGui/QPainter>
#include <gmock/gmock.h>
using ::testing::_;
using ::testing::Return;
using ::testing::InSequence;
using ::testing::AnyNumber;
using ::testing::Invoke;
using namespace dp;
using namespace std::placeholders;
namespace
{
class UploadedRender
{
public:
explicit UploadedRender(QPoint const & pen) : m_pen(pen) {}
void glMemoryToQImage(int x, int y, int w, int h, glConst f, glConst t, void const * memory)
{
TEST(f == gl_const::GLAlpha || f == gl_const::GLAlpha8 || f == gl_const::GLRed, ());
TEST(t == gl_const::GLUnsignedByteType, ());
uint8_t const * image = reinterpret_cast<uint8_t const *>(memory);
QPoint p(m_pen);
p.rx() += x;
m_images.push_back(qMakePair(p, CreateImage(w, h, image)));
m_pen.ry() += h;
}
void Render(QPaintDevice * device)
{
QPainter p(device);
for (auto const & d : m_images)
p.drawImage(d.first, d.second);
}
private:
QPoint m_pen;
QVector<QPair<QPoint, QImage>> m_images;
};
class DummyGlyphIndex : public GlyphIndex
{
public:
DummyGlyphIndex(m2::PointU size, ref_ptr<GlyphManager> mng)
: GlyphIndex(size, mng)
{}
ref_ptr<Texture::ResourceInfo> MapResource(GlyphKey const & key)
{
bool dummy = false;
return GlyphIndex::MapResource(key, dummy);
}
};
} // namespace
UNIT_TEST(UploadingGlyphs)
{
// Set QT_QPA_PLATFORM=offscreen env var to avoid running GUI on Linux
DrapeRoutine::Init();
EXPECTGL(glHasExtension(_)).Times(AnyNumber());
EXPECTGL(glBindTexture(_)).Times(AnyNumber());
EXPECTGL(glDeleteTexture(_)).Times(AnyNumber());
EXPECTGL(glTexParameter(_, _)).Times(AnyNumber());
EXPECTGL(glTexImage2D(_, _, _, _, _)).Times(AnyNumber());
EXPECTGL(glGenTexture()).Times(AnyNumber());
UploadedRender r(QPoint(10, 10));
dp::GlyphManager::Params args;
args.m_uniBlocks = base::JoinPath("fonts", "unicode_blocks.txt");
args.m_whitelist = base::JoinPath("fonts", "whitelist.txt");
args.m_blacklist = base::JoinPath("fonts", "blacklist.txt");
GetPlatform().GetFontNames(args.m_fonts);
uint32_t constexpr kTextureSize = 1024;
GlyphManager mng(args);
DummyGlyphIndex index(m2::PointU(kTextureSize, kTextureSize), make_ref(&mng));
size_t count = 1; // invalid symbol glyph has been mapped internally.
count += (index.MapResource(GlyphKey(0x58)) != nullptr) ? 1 : 0;
count += (index.MapResource(GlyphKey(0x59)) != nullptr) ? 1 : 0;
count += (index.MapResource(GlyphKey(0x61)) != nullptr) ? 1 : 0;
while (index.GetPendingNodesCount() < count)
;
TestingGraphicsContext context;
Texture::Params p;
p.m_allocator = GetDefaultAllocator(make_ref(&context));
p.m_format = dp::TextureFormat::Red;
p.m_width = p.m_height = kTextureSize;
DummyTexture tex;
tex.Create(make_ref(&context), p);
EXPECTGL(glTexSubImage2D(_, _, _, _, _, _, _))
.WillRepeatedly(Invoke(&r, &UploadedRender::glMemoryToQImage));
index.UploadResources(make_ref(&context), make_ref(&tex));
count = 0;
count += (index.MapResource(GlyphKey(0x68)) != nullptr) ? 1 : 0;
count += (index.MapResource(GlyphKey(0x30)) != nullptr) ? 1 : 0;
count += (index.MapResource(GlyphKey(0x62)) != nullptr) ? 1 : 0;
count += (index.MapResource(GlyphKey(0x65)) != nullptr) ? 1 : 0;
count += (index.MapResource(GlyphKey(0x400)) != nullptr) ? 1 : 0;
count += (index.MapResource(GlyphKey(0x401)) != nullptr) ? 1 : 0;
// TODO: Fix this condition
//while (index.GetPendingNodesCount() < count)
// ;
EXPECTGL(glTexSubImage2D(_, _, _, _, _, _, _))
.WillRepeatedly(Invoke(&r, &UploadedRender::glMemoryToQImage));
index.UploadResources(make_ref(&context), make_ref(&tex));
RunTestLoop("UploadingGlyphs", std::bind(&UploadedRender::Render, &r, _1));
DrapeRoutine::Shutdown();
}
*/

View file

@ -0,0 +1,374 @@
#include "drape/gl_functions.hpp"
#include "drape/drape_tests/gl_mock_functions.hpp"
#include "base/assert.hpp"
using namespace emul;
dp::ApiVersion GLFunctions::CurrentApiVersion = dp::ApiVersion::Invalid;
dp::GLExtensionsList GLFunctions::ExtensionsList;
#define MOCK_CALL(f) GLMockFunctions::Instance().f;
void GLFunctions::Init(dp::ApiVersion apiVersion)
{
MOCK_CALL(Init(apiVersion));
}
void GLFunctions::glFlush() {}
void GLFunctions::glFinish() {}
uint32_t GLFunctions::glGenVertexArray()
{
return MOCK_CALL(glGenVertexArray());
}
void GLFunctions::glBindVertexArray(uint32_t vao)
{
MOCK_CALL(glBindVertexArray(vao));
}
void GLFunctions::glDeleteVertexArray(uint32_t vao)
{
MOCK_CALL(glDeleteVertexArray(vao));
}
uint32_t GLFunctions::glGenBuffer()
{
return MOCK_CALL(glGenBuffer());
}
void GLFunctions::glBindBuffer(uint32_t vbo, glConst target)
{
MOCK_CALL(glBindBuffer(vbo, target));
}
void GLFunctions::glDeleteBuffer(uint32_t vbo)
{
MOCK_CALL(glDeleteBuffer(vbo));
}
void GLFunctions::glBufferData(glConst target, uint32_t size, void const * data, glConst usage)
{
MOCK_CALL(glBufferData(target, size, data, usage));
}
void GLFunctions::glBufferSubData(glConst target, uint32_t size, void const * data, uint32_t offset)
{
MOCK_CALL(glBufferSubData(target, size, data, offset));
}
uint32_t GLFunctions::glCreateShader(glConst type)
{
return MOCK_CALL(glCreateShader(type));
}
void GLFunctions::glShaderSource(uint32_t shaderID, std::string const & src, std::string const & defines)
{
MOCK_CALL(glShaderSource(shaderID, src));
}
bool GLFunctions::glCompileShader(uint32_t shaderID, std::string & errorLog)
{
return MOCK_CALL(glCompileShader(shaderID, errorLog));
}
void GLFunctions::glDeleteShader(uint32_t shaderID)
{
MOCK_CALL(glDeleteShader(shaderID));
}
uint32_t GLFunctions::glCreateProgram()
{
return MOCK_CALL(glCreateProgram());
}
void GLFunctions::glAttachShader(uint32_t programID, uint32_t shaderID)
{
MOCK_CALL(glAttachShader(programID, shaderID));
}
void GLFunctions::glDetachShader(uint32_t programID, uint32_t shaderID)
{
MOCK_CALL(glDetachShader(programID, shaderID));
}
bool GLFunctions::glLinkProgram(uint32_t programID, std::string & errorLog)
{
return MOCK_CALL(glLinkProgram(programID, errorLog));
}
void GLFunctions::glDeleteProgram(uint32_t programID)
{
MOCK_CALL(glDeleteProgram(programID));
}
void GLFunctions::glUseProgram(uint32_t programID)
{
MOCK_CALL(glUseProgram(programID));
}
int8_t GLFunctions::glGetAttribLocation(uint32_t programID, std::string const & name)
{
return MOCK_CALL(glGetAttribLocation(programID, name));
}
void GLFunctions::glBindAttribLocation(uint32_t programID, uint8_t index, std::string const & name) {}
// Enable vertex attribute binding. To get attributeLocation need to call glGetAttributeLocation.
void GLFunctions::glEnableVertexAttribute(int32_t attributeLocation)
{
MOCK_CALL(glEnableVertexAttribute(attributeLocation));
}
void GLFunctions::glVertexAttributePointer(int32_t attrLocation, uint32_t count, glConst type, bool needNormalize,
uint32_t stride, uint32_t offset)
{
MOCK_CALL(glVertexAttributePointer(attrLocation, count, type, needNormalize, stride, offset));
}
int8_t GLFunctions::glGetUniformLocation(uint32_t programID, std::string const & name)
{
return MOCK_CALL(glGetUniformLocation(programID, name));
}
void GLFunctions::glUniformValuei(int8_t location, int32_t v)
{
MOCK_CALL(glUniformValuei(location, v));
}
void GLFunctions::glUniformValuei(int8_t location, int32_t v1, int32_t v2)
{
MOCK_CALL(glUniformValuei(location, v1, v2));
}
void GLFunctions::glUniformValuei(int8_t location, int32_t v1, int32_t v2, int32_t v3)
{
MOCK_CALL(glUniformValuei(location, v1, v2, v3));
}
void GLFunctions::glUniformValuei(int8_t location, int32_t v1, int32_t v2, int32_t v3, int32_t v4)
{
MOCK_CALL(glUniformValuei(location, v1, v2, v3, v4));
}
void GLFunctions::glUniformValuef(int8_t location, float v)
{
MOCK_CALL(glUniformValuef(location, v));
}
void GLFunctions::glUniformValuef(int8_t location, float v1, float v2)
{
MOCK_CALL(glUniformValuef(location, v1, v2));
}
void GLFunctions::glUniformValuef(int8_t location, float v1, float v2, float v3)
{
MOCK_CALL(glUniformValuef(location, v1, v2, v3));
}
void GLFunctions::glUniformValuef(int8_t location, float v1, float v2, float v3, float v4)
{
MOCK_CALL(glUniformValuef(location, v1, v2, v3, v4));
}
void GLFunctions::glUniformMatrix4x4Value(int8_t location, float const * values)
{
MOCK_CALL(glUniformMatrix4x4Value(location, values));
}
uint32_t GLFunctions::glGetCurrentProgram()
{
return MOCK_CALL(glGetCurrentProgram());
}
bool GLFunctions::glHasExtension(std::string const & extName)
{
return MOCK_CALL(glHasExtension(extName));
}
int32_t GLFunctions::glGetProgramiv(uint32_t program, glConst paramName)
{
return MOCK_CALL(glGetProgramiv(program, paramName));
}
void GLFunctions::glGetActiveUniform(uint32_t programID, uint32_t uniformIndex, int32_t * uniformSize, glConst * type,
std::string & name)
{
MOCK_CALL(glGetActiveUniform(programID, uniformIndex, uniformSize, type, name));
}
void GLFunctions::glActiveTexture(glConst texBlock)
{
MOCK_CALL(glActiveTexture(texBlock));
}
uint32_t GLFunctions::glGenTexture()
{
MOCK_CALL(glGenTexture());
return 1;
}
void GLFunctions::glDeleteTexture(uint32_t id)
{
MOCK_CALL(glDeleteTexture(id));
}
void GLFunctions::glBindTexture(uint32_t textureID)
{
MOCK_CALL(glBindTexture(textureID));
}
void GLFunctions::glTexImage2D(int width, int height, glConst layout, glConst pixelType, void const * data)
{
MOCK_CALL(glTexImage2D(width, height, layout, pixelType, data));
}
void GLFunctions::glTexSubImage2D(int x, int y, int width, int height, glConst layout, glConst pixelType,
void const * data)
{
MOCK_CALL(glTexSubImage2D(x, y, width, height, layout, pixelType, data));
}
void GLFunctions::glTexParameter(glConst param, glConst value)
{
MOCK_CALL(glTexParameter(param, value));
}
int32_t GLFunctions::glGetInteger(glConst pname)
{
return MOCK_CALL(glGetInteger(pname));
}
std::string GLFunctions::glGetString(glConst pname)
{
return MOCK_CALL(glGetString(pname));
}
int32_t GLFunctions::glGetMaxLineWidth()
{
return MOCK_CALL(glGetMaxLineWidth());
}
void GLFunctions::glLineWidth(uint32_t value)
{
return MOCK_CALL(glLineWidth(value));
}
void GLFunctions::glViewport(uint32_t x, uint32_t y, uint32_t w, uint32_t h)
{
return MOCK_CALL(glViewport(x, y, w, h));
}
void GLFunctions::glScissor(uint32_t x, uint32_t y, uint32_t w, uint32_t h)
{
return MOCK_CALL(glScissor(x, y, w, h));
}
void GLFunctions::glGenFramebuffer(uint32_t * fbo)
{
return MOCK_CALL(glGenFramebuffer(fbo));
}
void GLFunctions::glBindFramebuffer(uint32_t fbo)
{
return MOCK_CALL(glBindFramebuffer(fbo));
}
void GLFunctions::glDeleteFramebuffer(uint32_t * fbo)
{
return MOCK_CALL(glDeleteFramebuffer(fbo));
}
void GLFunctions::glFramebufferTexture2D(glConst attachment, glConst texture)
{
return MOCK_CALL(glFramebufferTexture2D(attachment, texture));
}
uint32_t GLFunctions::glCheckFramebufferStatus()
{
return MOCK_CALL(glCheckFramebufferStatus());
}
void CheckGLError(base::SrcPoint const & /*srcPt*/) {}
void GLFunctions::glEnable(glConst mode) {}
void GLFunctions::glBlendEquation(glConst function) {}
void GLFunctions::glBlendFunc(glConst srcFactor, glConst dstFactor) {}
bool GLFunctions::CanEnableDebugMessages()
{
return false;
}
void GLFunctions::glDebugMessageCallback(TglDebugProc messageCallback, void * userParam) {}
void GLFunctions::glDebugMessageControl(glConst source, glConst type, glConst severity, int32_t count,
uint32_t const * ids, uint8_t enabled)
{}
void GLFunctions::glDisable(glConst mode) {}
void GLFunctions::glDepthFunc(glConst depthFunc) {}
void GLFunctions::glUniformValueiv(int8_t location, int32_t * v, uint32_t size) {}
void * GLFunctions::glMapBuffer(glConst, glConst)
{
return 0;
}
void * GLFunctions::glMapBufferRange(glConst, uint32_t, uint32_t, glConst)
{
return 0;
}
void GLFunctions::glUnmapBuffer(glConst target) {}
void GLFunctions::glDrawElements(glConst primitive, uint32_t sizeOfIndex, uint32_t indexCount, uint32_t startIndex) {}
void GLFunctions::glDrawArrays(glConst mode, int32_t first, uint32_t count) {}
void GLFunctions::glPixelStore(glConst name, uint32_t value) {}
void GLFunctions::glStencilOpSeparate(glConst face, glConst sfail, glConst dpfail, glConst dppass) {}
void GLFunctions::glStencilFuncSeparate(glConst face, glConst func, int ref, uint32_t mask) {}
int32_t GLFunctions::glGetBufferParameter(glConst target, glConst name)
{
return MOCK_CALL(glGetBufferParameter(target, name));
}
void GLFunctions::glCullFace(glConst face)
{
MOCK_CALL(glCullFace(face));
}
void GLFunctions::glFrontFace(glConst mode)
{
MOCK_CALL(glFrontFace(mode));
}
void GLFunctions::glDepthMask(bool needWriteToDepthBuffer)
{
MOCK_CALL(glDepthMask(needWriteToDepthBuffer));
}
void GLFunctions::glClear(uint32_t clearBits)
{
MOCK_CALL(glClear(clearBits));
}
void GLFunctions::glClearColor(float r, float g, float b, float a)
{
MOCK_CALL(glClearColor(r, g, b, a));
}
void GLFunctions::glClearDepthValue(double depth)
{
MOCK_CALL(glClearDepthValue(depth));
}

View file

@ -0,0 +1,28 @@
#include "drape/drape_tests/gl_mock_functions.hpp"
namespace emul
{
void GLMockFunctions::Init(int * argc, char ** argv)
{
::testing::InitGoogleMock(argc, argv);
m_mock = new GLMockFunctions();
}
void GLMockFunctions::Teardown()
{
delete m_mock;
m_mock = NULL;
}
GLMockFunctions & GLMockFunctions::Instance()
{
return *m_mock;
}
void GLMockFunctions::ValidateAndClear()
{
::testing::Mock::VerifyAndClear(m_mock);
}
GLMockFunctions * GLMockFunctions::m_mock;
} // namespace emul

View file

@ -0,0 +1,109 @@
#pragma once
#include "drape/drape_global.hpp"
#include "drape/gl_constants.hpp"
#include <gmock/gmock.h>
#include <string>
namespace emul
{
class GLMockFunctions
{
public:
static void Init(int * argc, char ** argv);
static void Teardown();
static GLMockFunctions & Instance();
static void ValidateAndClear();
MOCK_METHOD1(Init, void(dp::ApiVersion));
// VAO
MOCK_METHOD0(glGenVertexArray, uint32_t());
MOCK_METHOD1(glBindVertexArray, void(uint32_t vao));
MOCK_METHOD1(glDeleteVertexArray, void(uint32_t vao));
// VBO
MOCK_METHOD0(glGenBuffer, uint32_t());
MOCK_METHOD2(glBindBuffer, void(uint32_t vbo, glConst target));
MOCK_METHOD1(glDeleteBuffer, void(uint32_t vbo));
MOCK_METHOD4(glBufferData, void(glConst target, uint32_t size, void const * data, glConst usage));
MOCK_METHOD4(glBufferSubData, void(glConst target, uint32_t size, void const * data, uint32_t offset));
MOCK_METHOD2(glGetBufferParameter, int32_t(glConst target, glConst name));
MOCK_METHOD2(glGetUniformLocation, int8_t(uint32_t programID, std::string const & name));
MOCK_METHOD2(glUniformValuei, void(int8_t location, int32_t v));
MOCK_METHOD3(glUniformValuei, void(int8_t location, int32_t v1, int32_t v2));
MOCK_METHOD4(glUniformValuei, void(int8_t location, int32_t v1, int32_t v2, int32_t v3));
MOCK_METHOD5(glUniformValuei, void(int8_t location, int32_t v1, int32_t v2, int32_t v3, int32_t v4));
MOCK_METHOD2(glUniformValuef, void(int8_t location, float v));
MOCK_METHOD3(glUniformValuef, void(int8_t location, float v1, float v2));
MOCK_METHOD4(glUniformValuef, void(int8_t location, float v1, float v2, float v3));
MOCK_METHOD5(glUniformValuef, void(int8_t location, float v1, float v2, float v3, float v4));
MOCK_METHOD2(glUniformMatrix4x4Value, void(int8_t location, float const * values));
MOCK_METHOD0(glGetCurrentProgram, uint32_t());
MOCK_METHOD1(glCreateShader, uint32_t(glConst type));
MOCK_METHOD2(glShaderSource, void(uint32_t shaderID, std::string const & src));
MOCK_METHOD2(glCompileShader, bool(uint32_t shaderID, std::string & errorLog));
MOCK_METHOD1(glDeleteShader, void(uint32_t shaderID));
MOCK_METHOD0(glCreateProgram, uint32_t());
MOCK_METHOD2(glAttachShader, void(uint32_t programID, uint32_t shaderID));
MOCK_METHOD2(glDetachShader, void(uint32_t programID, uint32_t shaderID));
MOCK_METHOD2(glLinkProgram, bool(uint32_t programID, std::string & errorLog));
MOCK_METHOD1(glDeleteProgram, void(uint32_t programID));
MOCK_METHOD2(glGetAttribLocation, int32_t(uint32_t programID, std::string const & name));
MOCK_METHOD1(glEnableVertexAttribute, void(int32_t attributeLocation));
MOCK_METHOD6(glVertexAttributePointer, void(int32_t attrLocation, uint32_t count, glConst type, bool needNormalize,
uint32_t stride, uint32_t offset));
MOCK_METHOD1(glUseProgram, void(uint32_t programID));
MOCK_METHOD1(glHasExtension, bool(std::string const & extName));
MOCK_METHOD2(glGetProgramiv, int32_t(uint32_t, glConst));
MOCK_METHOD5(glGetActiveUniform, void(uint32_t, uint32_t, int32_t *, glConst *, std::string &));
// Texture functions
MOCK_METHOD1(glActiveTexture, void(glConst));
MOCK_METHOD0(glGenTexture, uint32_t());
MOCK_METHOD1(glDeleteTexture, void(uint32_t));
MOCK_METHOD1(glBindTexture, void(uint32_t));
MOCK_METHOD5(glTexImage2D, void(int, int, glConst, glConst, void const *));
MOCK_METHOD7(glTexSubImage2D, void(int, int, int, int, glConst, glConst, void const *));
MOCK_METHOD2(glTexParameter, void(glConst, glConst));
MOCK_METHOD1(glGetInteger, int32_t(glConst));
MOCK_METHOD1(glGetString, std::string(glConst));
MOCK_METHOD0(glGetMaxLineWidth, int32_t());
MOCK_METHOD1(glLineWidth, void(uint32_t value));
MOCK_METHOD4(glViewport, void(uint32_t x, uint32_t y, uint32_t w, uint32_t h));
MOCK_METHOD4(glScissor, void(uint32_t x, uint32_t y, uint32_t w, uint32_t h));
// FBO
MOCK_METHOD1(glGenFramebuffer, void(uint32_t * fbo));
MOCK_METHOD1(glBindFramebuffer, void(uint32_t fbo));
MOCK_METHOD1(glDeleteFramebuffer, void(uint32_t * fbo));
MOCK_METHOD2(glFramebufferTexture2D, void(glConst attachment, glConst texture));
MOCK_METHOD0(glCheckFramebufferStatus, uint32_t());
MOCK_METHOD1(glCullFace, void(glConst));
MOCK_METHOD1(glFrontFace, void(glConst));
MOCK_METHOD1(glDepthMask, void(bool));
MOCK_METHOD1(glClear, void(uint32_t));
MOCK_METHOD4(glClearColor, void(float, float, float, float));
MOCK_METHOD1(glClearDepthValue, void(double));
private:
static GLMockFunctions * m_mock;
};
} // namespace emul
#define EXPECTGL(x) EXPECT_CALL(emul::GLMockFunctions::Instance(), x)

View file

@ -0,0 +1,321 @@
#include "testing/testing.hpp"
#include "drape/drape_tests/img.hpp"
#include "drape/font_constants.hpp"
#include "drape/glyph_manager.hpp"
#include "drape/harfbuzz_shaping.hpp"
#include "base/file_name_utils.hpp"
#include "qt_tstfrm/test_main_loop.hpp"
#include "platform/platform.hpp"
#include <QtGui/QPainter>
#include <functional>
#include <iostream>
#include <vector>
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_MODULE_H
#include <hb-ft.h>
namespace glyph_mng_tests
{
class GlyphRenderer
{
FT_Library m_freetypeLibrary;
std::string m_utf8;
int m_fontPixelSize;
char const * m_lang;
static constexpr FT_Int kSdfSpread{dp::kSdfBorder};
public:
GlyphRenderer()
{
// Initialize FreeType
TEST_EQUAL(0, FT_Init_FreeType(&m_freetypeLibrary), ("Can't initialize FreeType"));
for (auto const module : {"sdf", "bsdf"})
TEST_EQUAL(0, FT_Property_Set(m_freetypeLibrary, module, "spread", &kSdfSpread), ());
dp::GlyphManager::Params args;
args.m_uniBlocks = base::JoinPath("fonts", "unicode_blocks.txt");
args.m_whitelist = base::JoinPath("fonts", "whitelist.txt");
args.m_blacklist = base::JoinPath("fonts", "blacklist.txt");
GetPlatform().GetFontNames(args.m_fonts);
m_mng = std::make_unique<dp::GlyphManager>(args);
}
~GlyphRenderer() { FT_Done_FreeType(m_freetypeLibrary); }
void SetString(std::string const & s, int fontPixelSize, char const * lang)
{
m_utf8 = s;
m_fontPixelSize = fontPixelSize;
m_lang = lang;
}
static float Smoothstep(float edge0, float edge1, float x)
{
x = std::clamp((x - edge0) / (edge1 - edge0), 0.0f, 1.0f);
return x * x * (3 - 2 * x);
}
static float PixelColorFromDistance(float distance)
{
// float const normalizedDistance = (distance - 128.f) / 128.f;
float const normalizedDistance = distance / 255.f;
static constexpr float kFontScale = 1.f;
static constexpr float kSmoothing = 0.25f / (kSdfSpread * kFontScale);
float const alpha = Smoothstep(0.5f - kSmoothing, 0.5f + kSmoothing, normalizedDistance);
return 255.f * alpha;
}
void RenderGlyphs(QPaintDevice * device) const
{
QPainter painter(device);
painter.fillRect(QRectF(0.0, 0.0, device->width(), device->height()), Qt::white);
auto const shapedText = m_mng->ShapeText(m_utf8, m_fontPixelSize, m_lang);
std::cout << "Total width: " << shapedText.m_lineWidthInPixels << '\n';
std::cout << "Max height: " << shapedText.m_maxLineHeightInPixels << '\n';
int constexpr kLineStartX = 10;
int constexpr kLineMarginY = 50;
QPoint pen(kLineStartX, kLineMarginY);
for (auto const & glyph : shapedText.m_glyphs)
{
constexpr bool kUseSdfBitmap = false;
dp::GlyphImage img = m_mng->GetGlyphImage(glyph.m_key, m_fontPixelSize, kUseSdfBitmap);
auto const w = img.m_width;
auto const h = img.m_height;
// Spaces do not have images.
if (w && h)
{
QPoint currentPen = pen;
currentPen.rx() += glyph.m_xOffset;
// Image is drawn at the top left origin, text metrics returns bottom left origin.
currentPen.ry() -= glyph.m_yOffset + h;
painter.drawImage(currentPen, CreateImage(w, h, img.m_data->data()), QRect(0, 0, w, h));
}
pen += QPoint(glyph.m_xAdvance, glyph.m_yAdvance /* 0 for horizontal texts */);
img.Destroy();
}
pen.rx() = kLineStartX;
pen.ry() += kLineMarginY;
for (auto const & glyph : shapedText.m_glyphs)
{
constexpr bool kUseSdfBitmap = true;
auto img = m_mng->GetGlyphImage(glyph.m_key, m_fontPixelSize, kUseSdfBitmap);
auto const w = img.m_width;
auto const h = img.m_height;
// Spaces do not have images.
if (w && h)
{
QPoint currentPen = pen;
currentPen.rx() += glyph.m_xOffset;
currentPen.ry() -= glyph.m_yOffset + h;
painter.drawImage(currentPen, CreateImage(w, h, img.m_data->data()),
QRect(dp::kSdfBorder, dp::kSdfBorder, w - 2 * dp::kSdfBorder, h - 2 * dp::kSdfBorder));
}
pen += QPoint(glyph.m_xAdvance, glyph.m_yAdvance /* 0 for horizontal texts */);
img.Destroy();
}
////////////////////////////////////////////////////////////////////////////
// Manual rendering using HB functions.
{
pen.rx() = kLineStartX;
pen.ry() += kLineMarginY;
auto const hbLanguage = hb_language_from_string(m_lang, -1);
auto const runs = harfbuzz_shaping::GetTextSegments(m_utf8);
for (auto const & segment : runs.m_segments)
{
hb_buffer_t * buf = hb_buffer_create();
hb_buffer_add_utf16(buf, reinterpret_cast<uint16_t const *>(runs.m_text.data()), runs.m_text.size(),
segment.m_start, segment.m_length);
hb_buffer_set_direction(buf, segment.m_direction);
hb_buffer_set_script(buf, segment.m_script);
hb_buffer_set_language(buf, hbLanguage);
// If direction, script, and language are not known.
// hb_buffer_guess_segment_properties(buf);
std::string const lang = m_lang;
std::string const fontFileName = lang == "ar" ? "00_NotoNaskhArabic-Regular.ttf" : "07_roboto_medium.ttf";
auto reader = GetPlatform().GetReader("fonts/" + fontFileName);
auto fontFile = reader->GetName();
FT_Face face;
if (FT_New_Face(m_freetypeLibrary, fontFile.c_str(), 0, &face))
{
std::cerr << "Can't load font " << fontFile << '\n';
return;
}
// Set character size
FT_Set_Pixel_Sizes(face, 0, m_fontPixelSize);
// This also works.
// if (FT_Set_Char_Size(face, 0, m_fontPixelSize << 6, 0, 0)) {
// std::cerr << "Can't set character size\n";
// return;
// }
// Set no transform (identity)
// FT_Set_Transform(face, nullptr, nullptr);
// Load font into HarfBuzz
hb_font_t * font = hb_ft_font_create(face, nullptr);
// Shape!
hb_shape(font, buf, nullptr, 0);
// Get the glyph and position information.
unsigned int glyph_count;
hb_glyph_info_t * glyph_info = hb_buffer_get_glyph_infos(buf, &glyph_count);
hb_glyph_position_t * glyph_pos = hb_buffer_get_glyph_positions(buf, &glyph_count);
for (unsigned int i = 0; i < glyph_count; i++)
{
hb_codepoint_t const glyphid = glyph_info[i].codepoint;
FT_Int32 const flags = FT_LOAD_RENDER;
FT_Load_Glyph(face, glyphid, flags);
FT_Render_Glyph(face->glyph, FT_RENDER_MODE_SDF);
FT_GlyphSlot slot = face->glyph;
FT_Bitmap const & ftBitmap = slot->bitmap;
auto const buffer = ftBitmap.buffer;
auto const width = ftBitmap.width;
auto const height = ftBitmap.rows;
for (unsigned h = 0; h < height; ++h)
{
for (unsigned w = 0; w < width; ++w)
{
auto curPixelAddr = buffer + h * width + w;
float currPixel = *curPixelAddr;
currPixel = PixelColorFromDistance(currPixel);
*curPixelAddr = static_cast<unsigned char>(currPixel);
}
}
auto const bearing_x = slot->metrics.horiBearingX; // slot->bitmap_left;
auto const bearing_y = slot->metrics.horiBearingY; // slot->bitmap_top;
auto const & glyphPos = glyph_pos[i];
hb_position_t const x_offset = (glyphPos.x_offset + bearing_x) >> 6;
hb_position_t const y_offset = (glyphPos.y_offset + bearing_y) >> 6;
hb_position_t const x_advance = glyphPos.x_advance >> 6;
hb_position_t const y_advance = glyphPos.y_advance >> 6;
// Empty images are possible for space characters.
if (width != 0 && height != 0)
{
QPoint currentPen = pen;
currentPen.rx() += x_offset;
currentPen.ry() -= y_offset;
painter.drawImage(currentPen, CreateImage(width, height, buffer),
QRect(kSdfSpread, kSdfSpread, width - 2 * kSdfSpread, height - 2 * kSdfSpread));
}
pen += QPoint(x_advance, y_advance);
}
// Tidy up.
hb_buffer_destroy(buf);
hb_font_destroy(font);
FT_Done_Face(face);
}
}
//////////////////////////////////////////////////////////////////
// QT text renderer.
{
pen.rx() = kLineStartX;
pen.ry() += kLineMarginY;
// QFont font("Noto Naskh Arabic");
QFont font("Roboto");
font.setPixelSize(m_fontPixelSize);
// font.setWeight(QFont::Weight::Normal);
painter.setFont(font);
painter.drawText(pen, QString::fromUtf8(m_utf8.c_str(), m_utf8.size()));
}
}
private:
std::unique_ptr<dp::GlyphManager> m_mng;
};
// This unit test creates a window so can't be run in GUI-less Linux machine.
// Make sure that the QT_QPA_PLATFORM=offscreen environment variable is set.
UNIT_TEST(GlyphLoadingTest)
{
GlyphRenderer renderer;
using namespace std::placeholders;
constexpr int fontSize = 27;
renderer.SetString("Muḩāfaz̧at", fontSize, "en");
RunTestLoop("Latin Extended", std::bind(&GlyphRenderer::RenderGlyphs, &renderer, _1));
renderer.SetString("Строка", fontSize, "ru");
RunTestLoop("ru", std::bind(&GlyphRenderer::RenderGlyphs, &renderer, _1));
renderer.SetString("ØŒÆ", fontSize, "en");
RunTestLoop("en", std::bind(&GlyphRenderer::RenderGlyphs, &renderer, _1));
renderer.SetString("𫝚 𫝛 𫝜", fontSize, "zh");
RunTestLoop("CJK Surrogates", std::bind(&GlyphRenderer::RenderGlyphs, &renderer, _1));
renderer.SetString(
"الحلّة گلها"
" كسول الزنجبيل القط"
"56"
"عين علي (الحربية)"
"123"
" اَلْعَرَبِيَّةُ",
fontSize, "ar");
RunTestLoop("Arabic1", std::bind(&GlyphRenderer::RenderGlyphs, &renderer, _1));
renderer.SetString(
"12345"
"گُلها"
"12345"
"گُلها"
"12345",
fontSize, "ar");
RunTestLoop("Arabic2", std::bind(&GlyphRenderer::RenderGlyphs, &renderer, _1));
renderer.SetString("മനക്കലപ്പടി", fontSize, "ml");
RunTestLoop("Malay", std::bind(&GlyphRenderer::RenderGlyphs, &renderer, _1));
renderer.SetString(
"Test 12 345 "
"گُلها"
"678 9000 Test",
fontSize, "ar");
RunTestLoop("Arabic Mixed", std::bind(&GlyphRenderer::RenderGlyphs, &renderer, _1));
renderer.SetString("NFKC Razdoĺny NFKD Razdoĺny", fontSize, "be");
RunTestLoop("Polish", std::bind(&GlyphRenderer::RenderGlyphs, &renderer, _1));
}
} // namespace glyph_mng_tests

View file

@ -0,0 +1,26 @@
#include "drape/font_texture.hpp"
#include "testing/testing.hpp"
UNIT_TEST(SimplePackTest)
{
dp::GlyphPacker packer(m2::PointU(32, 32));
m2::RectU r;
TEST(packer.PackGlyph(10, 13, r), ());
TEST_EQUAL(r, m2::RectU(0, 0, 10, 13), ());
TEST(packer.PackGlyph(18, 8, r), ());
TEST_EQUAL(r, m2::RectU(10, 0, 28, 8), ());
TEST(packer.PackGlyph(4, 15, r), ());
TEST_EQUAL(r, m2::RectU(28, 0, 32, 15), ());
TEST(packer.PackGlyph(7, 10, r), ());
TEST(!packer.IsFull(), ());
TEST_EQUAL(r, m2::RectU(0, 15, 7, 25), ());
TEST(!packer.PackGlyph(12, 18, r), ());
TEST(packer.IsFull(), ());
TEST_EQUAL(r, m2::RectU(0, 15, 7, 25), ());
}

View file

@ -0,0 +1,26 @@
#include "drape/harfbuzz_shaping.hpp"
#include "testing/testing.hpp"
namespace harfbuzz_shaping
{
bool operator==(TextSegment const & s1, TextSegment const & s2)
{
return s1.m_start == s2.m_start && s1.m_length == s2.m_length && s1.m_script == s2.m_script &&
s1.m_direction == s2.m_direction;
}
UNIT_TEST(GetTextSegments)
{
auto const [text, segments] = GetTextSegments("Map data © OpenStreetMap");
TEST(text == u"Map data © OpenStreetMap", ());
std::vector<TextSegment> const expected{
{0, 3, HB_SCRIPT_LATIN, HB_DIRECTION_LTR}, {3, 1, HB_SCRIPT_COMMON, HB_DIRECTION_LTR},
{4, 4, HB_SCRIPT_LATIN, HB_DIRECTION_LTR}, {8, 3, HB_SCRIPT_COMMON, HB_DIRECTION_LTR},
{11, 13, HB_SCRIPT_LATIN, HB_DIRECTION_LTR},
};
TEST_EQUAL(segments, expected, ());
}
} // namespace harfbuzz_shaping

View file

@ -0,0 +1,24 @@
#include "drape/drape_tests/img.hpp"
void cleanUpQImageMemory(void * mem)
{
free(mem);
}
QImage CreateImage(uint32_t w, uint32_t h, uint8_t const * mem)
{
int pitch = 32 * (((w - 1) / 32) + 1);
int byteCount = pitch * h;
unsigned char * buf = (unsigned char *)malloc(byteCount);
memset(buf, 0, byteCount);
for (uint32_t i = 0; i < h; ++i)
memcpy(buf + pitch * i, mem + w * i, w);
QImage img = QImage(buf, pitch, h, QImage::Format_Indexed8, &cleanUpQImageMemory, buf);
img.setColorCount(0xFF);
for (int i = 0; i < 256; ++i)
img.setColor(i, qRgb(255 - i, 255 - i, 255 - i));
return img;
}

View file

@ -0,0 +1,5 @@
#pragma once
#include <QtGui/QImage>
QImage CreateImage(uint32_t w, uint32_t h, uint8_t const * mem);

View file

@ -0,0 +1,49 @@
#pragma once
#include "base/logging.hpp"
#include "drape/gl_constants.hpp"
#include "testing/testing.hpp"
#include <cstring>
namespace dp
{
struct MemoryComparer
{
void * m_mem;
int m_size;
MemoryComparer(void * memory, int size) : m_mem(memory), m_size(size) {}
void cmpSubBuffer(glConst /*type*/, uint32_t size, void const * data, uint32_t /*offset*/) const
{
TEST_EQUAL(size, static_cast<uint32_t>(m_size), ());
TEST_EQUAL(memcmp(m_mem, data, size), 0, ());
}
void cmpSubImage(uint32_t /*x*/, uint32_t /*y*/, uint32_t width, uint32_t height, glConst layout, glConst pixelFormat,
void const * data) const
{
uint32_t channelCount = 0;
if (layout == gl_const::GLRGBA || layout == gl_const::GLRGBA8 || layout == gl_const::GLRGBA4)
channelCount = 4;
else if (layout == gl_const::GLRGB)
channelCount = 3;
else if (layout == gl_const::GLAlpha || layout == gl_const::GLAlpha8 || layout == gl_const::GLLuminance ||
layout == gl_const::GLLuminance8 || layout == gl_const::GLAlphaLuminance || layout == gl_const::GLRed)
{
channelCount = 1;
}
else
ASSERT(false, ());
ASSERT(gl_const::GL8BitOnChannel == pixelFormat, ());
TEST_EQUAL(static_cast<uint32_t>(m_size), width * height * channelCount, ());
uint8_t * member = (uint8_t *)m_mem;
uint8_t * input = (uint8_t *)data;
for (int i = 0; i < m_size; ++i)
TEST_EQUAL(member[i], input[i], (i));
}
};
} // namespace dp

View file

@ -0,0 +1,75 @@
#include "testing/testing.hpp"
#include "drape/object_pool.hpp"
class vec2
{
public:
class vec2Factory
{
public:
vec2 * GetNew() const { return new vec2(); }
};
public:
float m_x, m_y;
static int m_counter;
vec2() : m_x(0.0f), m_y(0.0f) { m_counter++; }
~vec2() { m_counter--; }
};
int vec2::m_counter = 0;
UNIT_TEST(ObjectPoolFilling)
{
vec2::vec2Factory factory;
dp::ObjectPool<vec2, vec2::vec2Factory> pool(1, factory);
vec2 *pt1, *pt2, *pt3;
pt1 = pool.Get();
pool.Return(pt1);
pt2 = pool.Get();
TEST_EQUAL(pt1, pt2, ());
pt3 = pool.Get();
pool.Return(pt2);
pool.Return(pt3);
}
UNIT_TEST(ObjectPoolClearing_1)
{
vec2::m_counter = 0;
vec2::vec2Factory factory;
auto pool = new dp::ObjectPool<vec2, vec2::vec2Factory>(1, factory);
vec2 *pt1, *pt2, *pt3;
pt1 = pool->Get();
pool->Return(pt1);
pt2 = pool->Get();
pt3 = pool->Get();
pool->Return(pt2);
pool->Return(pt3);
delete pool;
TEST_EQUAL(vec2::m_counter, 0, ());
}
UNIT_TEST(ObjectPoolClearing_2)
{
vec2::m_counter = 0;
vec2::vec2Factory factory;
auto pool = new dp::ObjectPool<vec2, vec2::vec2Factory>(100, factory);
vec2 *pt1, *pt2, *pt3;
pt1 = pool->Get();
pool->Return(pt1);
pt2 = pool->Get();
pt3 = pool->Get();
pool->Return(pt2);
pool->Return(pt3);
delete pool;
TEST_EQUAL(vec2::m_counter, 0, ());
}

View file

@ -0,0 +1,97 @@
#include "drape/pointers.hpp"
#include "testing/testing.hpp"
#include "base/base.hpp"
#include <algorithm>
#include <string>
#include <utility>
namespace
{
class Tester
{
public:
Tester() = default;
};
#if defined(TRACK_POINTERS)
bool g_assertRaised = false;
bool OnAssertRaised(base::SrcPoint const & /* srcPoint */, std::string const & /* msg */)
{
g_assertRaised = true;
return false;
}
#endif
} // namespace
UNIT_TEST(PointersTrackingTest)
{
#if defined(TRACK_POINTERS)
DpPointerTracker::TAlivePointers const & alivePointers = DpPointerTracker::Instance().GetAlivePointers();
drape_ptr<Tester> ptr = make_unique_dp<Tester>();
void * ptrAddress = ptr.get();
std::string const ptrTypeName = typeid(Tester *).name();
// no references
TEST(alivePointers.find(ptrAddress) == alivePointers.end(), ());
// create a reference
ref_ptr<Tester> refPtr = make_ref(ptr);
DpPointerTracker::TAlivePointers::const_iterator found = alivePointers.find(ptrAddress);
TEST(found != alivePointers.end(), ());
TEST_EQUAL(found->second.first, 1, ());
TEST_EQUAL(found->second.second, ptrTypeName, ());
// copy reference
ref_ptr<Tester> refPtr2 = refPtr;
found = alivePointers.find(ptrAddress);
TEST_EQUAL(found->second.first, 2, ());
// remove reference
{
ref_ptr<Tester> refPtrInScope = refPtr2;
TEST_EQUAL(found->second.first, 3, ());
}
TEST_EQUAL(found->second.first, 2, ());
// move reference
ref_ptr<Tester> refPtr3 = std::move(refPtr2);
TEST_EQUAL(found->second.first, 2, ());
// assign reference
ref_ptr<Tester> refPtr4;
refPtr4 = refPtr3;
TEST_EQUAL(found->second.first, 3, ());
// move-assign reference
refPtr4 = std::move(refPtr3);
TEST_EQUAL(found->second.first, 2, ());
// create another reference
ref_ptr<Tester> refPtr5 = make_ref(ptr);
TEST_EQUAL(found->second.first, 3, ());
#endif
}
UNIT_TEST(RefPointerExpiringTest)
{
#if defined(TRACK_POINTERS)
g_assertRaised = false;
base::AssertFailedFn prevFn = base::SetAssertFunction(OnAssertRaised);
drape_ptr<Tester> ptr = make_unique_dp<Tester>();
ref_ptr<Tester> refPtr1 = make_ref(ptr);
ref_ptr<Tester> refPtr2 = make_ref(ptr);
ptr.reset();
base::SetAssertFunction(prevFn);
TEST(g_assertRaised, ());
#endif
}

View file

@ -0,0 +1,31 @@
#include "testing/testing.hpp"
#include "indexer/map_style.hpp"
#include "indexer/map_style_reader.hpp"
#include "drape/drape_tests/testing_graphics_context.hpp"
#include "drape/static_texture.hpp"
#include <string>
#include <vector>
UNIT_TEST(CheckTrafficArrowTextures)
{
static std::vector<std::string> skinPaths = {"6plus", "mdpi", "hdpi", "xhdpi", "xxhdpi", "xxxhdpi"};
static std::vector<MapStyle> styles = {MapStyle::MapStyleDefaultLight, MapStyle::MapStyleDefaultDark,
MapStyle::MapStyleVehicleLight, MapStyle::MapStyleVehicleDark};
TestingGraphicsContext context;
for (auto const & style : styles)
{
GetStyleReader().SetCurrentStyle(style);
for (auto const & skinPath : skinPaths)
{
dp::StaticTexture texture(make_ref(&context), "traffic-arrow.png", skinPath, dp::TextureFormat::RGBA8, nullptr);
TEST(texture.IsLoadingCorrect(), ());
dp::StaticTexture texture2(make_ref(&context), "area-hatching.png", skinPath, dp::TextureFormat::RGBA8, nullptr);
TEST(texture2.IsLoadingCorrect(), ());
}
}
}

View file

@ -0,0 +1,77 @@
#include "testing/testing.hpp"
#include "drape/drape_tests/dummy_texture.hpp"
#include "drape/drape_tests/memory_comparer.hpp"
#include "drape/stipple_pen_resource.hpp"
#include "drape/texture.hpp"
#include "drape/tm_read_resources.hpp"
namespace stipple_pen_tests
{
using namespace dp;
namespace
{
void TestPacker(StipplePenPacker & packer, uint32_t width, m2::RectU const & expect)
{
TEST_EQUAL(packer.PackResource({width, 1}), expect, ());
}
bool IsRectsEqual(m2::RectF const & r1, m2::RectF const & r2)
{
return AlmostEqualULPs(r1.minX(), r2.minX()) && AlmostEqualULPs(r1.minY(), r2.minY()) &&
AlmostEqualULPs(r1.maxX(), r2.maxX()) && AlmostEqualULPs(r1.maxY(), r2.maxY());
}
} // namespace
UNIT_TEST(StippleTest_Pack)
{
StipplePenPacker packer(m2::PointU(512, 8));
TestPacker(packer, 30, m2::RectU(0, 0, 30, 1));
TestPacker(packer, 254, m2::RectU(0, 1, 254, 2));
TestPacker(packer, 1, m2::RectU(0, 2, 1, 3));
TestPacker(packer, 250, m2::RectU(0, 3, 250, 4));
TestPacker(packer, 249, m2::RectU(0, 4, 249, 5));
m2::RectF mapped = packer.MapTextureCoords(m2::RectU(0, 0, 256, 1));
TEST(IsRectsEqual(mapped, m2::RectF(0.5f / 512.0f, 0.5f / 8.0f, 255.5f / 512.0f, 0.5f / 8.0f)), ());
}
UNIT_TEST(StippleTest_EqualPatterns)
{
using PatternT = std::array<double, 2>;
std::vector<PatternT> patterns;
using namespace dp::impl;
ParsePatternsList("./data/patterns.txt", [&patterns](buffer_vector<double, 8> const & p)
{
if (p.size() == 2)
patterns.push_back({p[0], p[1]});
});
auto const IsEqualPatterns = [](PatternT const & p1, PatternT const & p2)
{
for (double scale : {1, 2, 3})
if ((PatternFloat2Pixel(scale * p1[0]) != PatternFloat2Pixel(scale * p2[0])) ||
(PatternFloat2Pixel(scale * p1[1]) != PatternFloat2Pixel(scale * p2[1])))
return false;
return true;
};
auto const IsAlmostEqualPatterns = [](PatternT const & p1, PatternT const & p2)
{
double const scale = 3;
return (fabs(scale * p1[0] - scale * p2[0]) + fabs(scale * p1[1] - scale * p2[1])) < 1;
};
size_t const count = patterns.size();
for (size_t i = 0; i < count - 1; ++i)
{
for (size_t j = i + 1; j < count; ++j)
if (IsEqualPatterns(patterns[i], patterns[j]))
LOG(LINFO, ("Equal:", patterns[i], patterns[j]));
else if (IsAlmostEqualPatterns(patterns[i], patterns[j]))
LOG(LINFO, ("Almost equal:", patterns[i], patterns[j]));
}
}
} // namespace stipple_pen_tests

View file

@ -0,0 +1,43 @@
#pragma once
#include "drape/graphics_context.hpp"
// Testing context simulates OpenGLES3 API version.
class TestingGraphicsContext : public dp::GraphicsContext
{
public:
TestingGraphicsContext() = default;
explicit TestingGraphicsContext(dp::ApiVersion apiVersion) : m_apiVersion(apiVersion) {}
void Present() override {}
void MakeCurrent() override {}
void SetFramebuffer(ref_ptr<dp::BaseFramebuffer> framebuffer) override {}
void ForgetFramebuffer(ref_ptr<dp::BaseFramebuffer> framebuffer) override {}
void ApplyFramebuffer(std::string const & framebufferLabel) override {}
void Init(dp::ApiVersion apiVersion) override {}
dp::ApiVersion GetApiVersion() const override { return m_apiVersion; }
std::string GetRendererName() const override { return {}; }
std::string GetRendererVersion() const override { return {}; }
void PushDebugLabel(std::string const & label) override {}
void PopDebugLabel() override {}
void SetClearColor(dp::Color const & color) override {}
void Clear(uint32_t clearBits, uint32_t storeBits) override {}
void Flush() override {}
void SetViewport(uint32_t x, uint32_t y, uint32_t w, uint32_t h) override {}
void SetScissor(uint32_t x, uint32_t y, uint32_t w, uint32_t h) override {}
void SetDepthTestEnabled(bool enabled) override {}
void SetDepthTestFunction(dp::TestFunction depthFunction) override {}
void SetStencilTestEnabled(bool enabled) override {}
void SetStencilFunction(dp::StencilFace face, dp::TestFunction stencilFunction) override {}
void SetStencilActions(dp::StencilFace face, dp::StencilAction stencilFailAction, dp::StencilAction depthFailAction,
dp::StencilAction passAction) override
{}
void SetStencilReferenceValue(uint32_t stencilReferenceValue) override {}
void SetCullingEnabled(bool enabled) override {}
private:
dp::ApiVersion m_apiVersion = dp::ApiVersion::OpenGLES3;
};

View file

@ -0,0 +1,346 @@
#include "testing/testing.hpp"
#include "drape/drape_tests/dummy_texture.hpp"
#include "drape/drape_tests/memory_comparer.hpp"
#include "drape/drape_tests/testing_graphics_context.hpp"
#include "drape/gl_constants.hpp"
#include "drape/texture.hpp"
#include "drape/texture_of_colors.hpp"
#include "drape/drape_tests/gl_mock_functions.hpp"
#include <gmock/gmock.h>
using testing::_;
using testing::AnyOf;
using testing::IgnoreResult;
using testing::InSequence;
using testing::Invoke;
using testing::Return;
using namespace dp;
namespace
{
void TestRects(m2::RectF const & a, m2::RectF const & b)
{
TEST_ALMOST_EQUAL_ULPS(a.minX(), b.minX(), ());
TEST_ALMOST_EQUAL_ULPS(a.maxX(), b.maxX(), ());
TEST_ALMOST_EQUAL_ULPS(a.minY(), b.minY(), ());
TEST_ALMOST_EQUAL_ULPS(a.maxY(), b.maxY(), ());
}
void InitOpenGLTextures(int const w, int const h)
{
InSequence seq;
EXPECTGL(glHasExtension(_)).WillRepeatedly(Return(true));
EXPECTGL(glGenTexture()).WillOnce(Return(1));
EXPECTGL(glBindTexture(1)).WillOnce(Return());
EXPECTGL(glTexImage2D(w, h, AnyOf(gl_const::GLRGBA, gl_const::GLRGBA8), gl_const::GL8BitOnChannel, NULL));
EXPECTGL(glTexParameter(gl_const::GLMinFilter, gl_const::GLLinear));
EXPECTGL(glTexParameter(gl_const::GLMagFilter, gl_const::GLLinear));
EXPECTGL(glTexParameter(gl_const::GLWrapS, gl_const::GLClampToEdge));
EXPECTGL(glTexParameter(gl_const::GLWrapT, gl_const::GLClampToEdge));
EXPECTGL(glBindTexture(0)).WillOnce(Return());
}
class DummyColorPallete : public ColorPalette
{
typedef ColorPalette TBase;
public:
explicit DummyColorPallete(m2::PointU const & size) : TBase(size) {}
ref_ptr<Texture::ResourceInfo> MapResource(ColorKey const & key)
{
bool dummy = false;
return TBase::MapResource(key, dummy);
}
};
} // namespace
UNIT_TEST(ColorPalleteMappingTests)
{
DummyColorPallete cp(m2::PointU(32, 16));
ref_ptr<Texture::ResourceInfo> info1 = cp.MapResource(dp::ColorKey(dp::Color(0, 0, 0, 0)));
ref_ptr<Texture::ResourceInfo> info2 = cp.MapResource(dp::ColorKey(dp::Color(1, 1, 1, 1)));
ref_ptr<Texture::ResourceInfo> info3 = cp.MapResource(dp::ColorKey(dp::Color(0, 0, 0, 0)));
TEST_NOT_EQUAL(info1, info2, ());
TEST_EQUAL(info1, info3, ());
TestRects(info1->GetTexRect(), m2::RectF(1.0f / 32.0f, 1.0f / 16, 1.0f / 32.0f, 1.0f / 16));
TestRects(info2->GetTexRect(), m2::RectF(3.0f / 32.0f, 1.0f / 16, 3.0f / 32.0f, 1.0f / 16));
TestRects(info3->GetTexRect(), m2::RectF(1.0f / 32.0f, 1.0f / 16, 1.0f / 32.0f, 1.0f / 16));
for (int i = 2; i < 100; ++i)
cp.MapResource(dp::ColorKey(dp::Color(i, i, i, i)));
TestRects(cp.MapResource(dp::ColorKey(dp::Color(54, 54, 54, 54)))->GetTexRect(),
m2::RectF(13.0f / 32.0f, 7.0f / 16.0f, 13.0f / 32.0f, 7.0f / 16.0f));
}
UNIT_TEST(ColorPalleteUploadingSingleRow)
{
int const width = 32;
int const height = 16;
InitOpenGLTextures(width, height);
TestingGraphicsContext context;
Texture::Params p;
p.m_allocator = GetDefaultAllocator(make_ref(&context));
p.m_format = dp::TextureFormat::RGBA8;
p.m_width = width;
p.m_height = height;
DummyTexture texture;
texture.Create(make_ref(&context), p);
DummyColorPallete cp(m2::PointU(width, height));
cp.UploadResources(make_ref(&context), make_ref(&texture));
{
cp.MapResource(dp::ColorKey(dp::Color(0xFF, 0, 0, 0)));
cp.MapResource(dp::ColorKey(dp::Color(0, 0xFF, 0, 0)));
cp.MapResource(dp::ColorKey(dp::Color(0, 0, 0xFF, 0)));
cp.MapResource(dp::ColorKey(dp::Color(0, 0, 0, 0xFF)));
cp.MapResource(dp::ColorKey(dp::Color(0xAA, 0xBB, 0xCC, 0xDD)));
uint8_t memoryEtalon[] = {
0xFF, 0x00, 0x00, 0x00, // 1 pixel (1st row)
0xFF, 0x00, 0x00, 0x00, // 1 pixel (1st row)
0x00, 0xFF, 0x00, 0x00, // 2 pixel (1st row)
0x00, 0xFF, 0x00, 0x00, // 2 pixel (1st row)
0x00, 0x00, 0xFF, 0x00, // 3 pixel (1st row)
0x00, 0x00, 0xFF, 0x00, // 3 pixel (1st row)
0x00, 0x00, 0x00, 0xFF, // 4 pixel (1st row)
0x00, 0x00, 0x00, 0xFF, // 4 pixel (1st row)
0xAA, 0xBB, 0xCC, 0xDD, // 5 pixel (1st row)
0xAA, 0xBB, 0xCC, 0xDD, // 5 pixel (1st row)
0xFF, 0x00, 0x00, 0x00, // 1 pixel (2nd row)
0xFF, 0x00, 0x00, 0x00, // 1 pixel (2nd row)
0x00, 0xFF, 0x00, 0x00, // 2 pixel (2nd row)
0x00, 0xFF, 0x00, 0x00, // 2 pixel (2nd row)
0x00, 0x00, 0xFF, 0x00, // 3 pixel (2nd row)
0x00, 0x00, 0xFF, 0x00, // 3 pixel (2nd row)
0x00, 0x00, 0x00, 0xFF, // 4 pixel (2nd row)
0x00, 0x00, 0x00, 0xFF, // 4 pixel (2nd row)
0xAA, 0xBB, 0xCC, 0xDD, // 5 pixel (2nd row)
0xAA, 0xBB, 0xCC, 0xDD // 5 pixel (2nd row)
};
MemoryComparer cmp(memoryEtalon, ARRAY_SIZE(memoryEtalon));
EXPECTGL(glTexSubImage2D(0, 0, 10, 2, AnyOf(gl_const::GLRGBA, gl_const::GLRGBA8), gl_const::GL8BitOnChannel, _))
.WillOnce(Invoke(&cmp, &MemoryComparer::cmpSubImage));
cp.UploadResources(make_ref(&context), make_ref(&texture));
}
{
cp.MapResource(dp::ColorKey(dp::Color(0xFF, 0xAA, 0, 0)));
cp.MapResource(dp::ColorKey(dp::Color(0xAA, 0xFF, 0, 0)));
cp.MapResource(dp::ColorKey(dp::Color(0xAA, 0, 0xFF, 0)));
cp.MapResource(dp::ColorKey(dp::Color(0xAA, 0, 0, 0xFF)));
cp.MapResource(dp::ColorKey(dp::Color(0x00, 0xBB, 0xCC, 0xDD)));
uint8_t memoryEtalon[] = {
0xFF, 0xAA, 0x00, 0x00, // 1 pixel (1st row)
0xFF, 0xAA, 0x00, 0x00, // 1 pixel (1st row)
0xAA, 0xFF, 0x00, 0x00, // 2 pixel (1st row)
0xAA, 0xFF, 0x00, 0x00, // 2 pixel (1st row)
0xAA, 0x00, 0xFF, 0x00, // 3 pixel (1st row)
0xAA, 0x00, 0xFF, 0x00, // 3 pixel (1st row)
0xAA, 0x00, 0x00, 0xFF, // 4 pixel (1st row)
0xAA, 0x00, 0x00, 0xFF, // 4 pixel (1st row)
0x00, 0xBB, 0xCC, 0xDD, // 5 pixel (1st row)
0x00, 0xBB, 0xCC, 0xDD, // 5 pixel (1st row)
0xFF, 0xAA, 0x00, 0x00, // 1 pixel (2nd row)
0xFF, 0xAA, 0x00, 0x00, // 1 pixel (2nd row)
0xAA, 0xFF, 0x00, 0x00, // 2 pixel (2nd row)
0xAA, 0xFF, 0x00, 0x00, // 2 pixel (2nd row)
0xAA, 0x00, 0xFF, 0x00, // 3 pixel (2nd row)
0xAA, 0x00, 0xFF, 0x00, // 3 pixel (2nd row)
0xAA, 0x00, 0x00, 0xFF, // 4 pixel (2nd row)
0xAA, 0x00, 0x00, 0xFF, // 4 pixel (2nd row)
0x00, 0xBB, 0xCC, 0xDD, // 5 pixel (2nd row)
0x00, 0xBB, 0xCC, 0xDD // 5 pixel (2nd row)
};
MemoryComparer cmp(memoryEtalon, ARRAY_SIZE(memoryEtalon));
EXPECTGL(glTexSubImage2D(10, 0, 10, 2, AnyOf(gl_const::GLRGBA, gl_const::GLRGBA8), gl_const::GL8BitOnChannel, _))
.WillOnce(Invoke(&cmp, &MemoryComparer::cmpSubImage));
cp.UploadResources(make_ref(&context), make_ref(&texture));
}
EXPECTGL(glDeleteTexture(1));
}
UNIT_TEST(ColorPalleteUploadingPartialyRow)
{
int const width = 8;
int const height = 8;
InitOpenGLTextures(width, height);
TestingGraphicsContext context;
Texture::Params p;
p.m_allocator = GetDefaultAllocator(make_ref(&context));
p.m_format = dp::TextureFormat::RGBA8;
p.m_width = width;
p.m_height = height;
DummyTexture texture;
texture.Create(make_ref(&context), p);
DummyColorPallete cp(m2::PointU(width, height));
{
cp.MapResource(dp::ColorKey(dp::Color(0xFF, 0, 0, 0)));
cp.MapResource(dp::ColorKey(dp::Color(0xFF, 0xFF, 0, 0)));
uint8_t memoryEtalon[] = {
0xFF, 0x00, 0x00, 0x00, // 1 pixel
0xFF, 0x00, 0x00, 0x00, // 1 pixel
0xFF, 0xFF, 0x00, 0x00, // 2 pixel
0xFF, 0xFF, 0x00, 0x00, // 2 pixel
0xFF, 0x00, 0x00, 0x00, // 1 pixel
0xFF, 0x00, 0x00, 0x00, // 1 pixel
0xFF, 0xFF, 0x00, 0x00, // 2 pixel
0xFF, 0xFF, 0x00, 0x00 // 2 pixel
};
MemoryComparer cmp(memoryEtalon, ARRAY_SIZE(memoryEtalon));
EXPECTGL(glTexSubImage2D(0, 0, 4, 2, AnyOf(gl_const::GLRGBA, gl_const::GLRGBA8), gl_const::GL8BitOnChannel, _))
.WillOnce(Invoke(&cmp, &MemoryComparer::cmpSubImage));
cp.UploadResources(make_ref(&context), make_ref(&texture));
}
{
cp.MapResource(dp::ColorKey(dp::Color(0xAA, 0, 0, 0)));
cp.MapResource(dp::ColorKey(dp::Color(0xAA, 0xAA, 0, 0)));
cp.MapResource(dp::ColorKey(dp::Color(0xAA, 0xAA, 0xAA, 0)));
cp.MapResource(dp::ColorKey(dp::Color(0xAA, 0xAA, 0xAA, 0xAA)));
uint8_t memoryEtalon1[] = {
0xAA, 0x00, 0x00, 0x00, // 1 pixel
0xAA, 0x00, 0x00, 0x00, // 1 pixel
0xAA, 0xAA, 0x00, 0x00, // 2 pixel
0xAA, 0xAA, 0x00, 0x00, // 2 pixel
0xAA, 0x00, 0x00, 0x00, // 1 pixel
0xAA, 0x00, 0x00, 0x00, // 1 pixel
0xAA, 0xAA, 0x00, 0x00, // 2 pixel
0xAA, 0xAA, 0x00, 0x00 // 2 pixel
};
uint8_t memoryEtalon2[] = {
0xAA, 0xAA, 0xAA, 0x00, // 1 pixel
0xAA, 0xAA, 0xAA, 0x00, // 1 pixel
0xAA, 0xAA, 0xAA, 0xAA, // 2 pixel
0xAA, 0xAA, 0xAA, 0xAA, // 2 pixel
0xAA, 0xAA, 0xAA, 0x00, // 1 pixel
0xAA, 0xAA, 0xAA, 0x00, // 1 pixel
0xAA, 0xAA, 0xAA, 0xAA, // 2 pixel
0xAA, 0xAA, 0xAA, 0xAA // 2 pixel
};
MemoryComparer cmp1(memoryEtalon1, ARRAY_SIZE(memoryEtalon1));
EXPECTGL(glTexSubImage2D(4, 0, 4, 2, AnyOf(gl_const::GLRGBA, gl_const::GLRGBA8), gl_const::GL8BitOnChannel, _))
.WillOnce(Invoke(&cmp1, &MemoryComparer::cmpSubImage));
MemoryComparer cmp2(memoryEtalon2, ARRAY_SIZE(memoryEtalon2));
EXPECTGL(glTexSubImage2D(0, 2, 4, 2, AnyOf(gl_const::GLRGBA, gl_const::GLRGBA8), gl_const::GL8BitOnChannel, _))
.WillOnce(Invoke(&cmp2, &MemoryComparer::cmpSubImage));
cp.UploadResources(make_ref(&context), make_ref(&texture));
}
EXPECTGL(glDeleteTexture(1));
}
UNIT_TEST(ColorPalleteUploadingMultiplyRow)
{
int const width = 4;
int const height = 8;
InitOpenGLTextures(width, height);
TestingGraphicsContext context;
Texture::Params p;
p.m_allocator = GetDefaultAllocator(make_ref(&context));
p.m_format = dp::TextureFormat::RGBA8;
p.m_width = width;
p.m_height = height;
DummyTexture texture;
texture.Create(make_ref(&context), p);
DummyColorPallete cp(m2::PointU(width, height));
cp.SetIsDebug(true);
{
cp.MapResource(dp::ColorKey(dp::Color(0xFF, 0, 0, 0)));
cp.MapResource(dp::ColorKey(dp::Color(0xAA, 0, 0, 0)));
uint8_t memoryEtalon[] = {
0xFF, 0x00, 0x00, 0x00, // 1 pixel
0xFF, 0x00, 0x00, 0x00, // 1 pixel
0xAA, 0x00, 0x00, 0x00, // 2 pixel
0xAA, 0x00, 0x00, 0x00, // 2 pixel
0xFF, 0x00, 0x00, 0x00, // 1 pixel
0xFF, 0x00, 0x00, 0x00, // 1 pixel
0xAA, 0x00, 0x00, 0x00, // 2 pixel
0xAA, 0x00, 0x00, 0x00, // 2 pixel
};
MemoryComparer cmp(memoryEtalon, ARRAY_SIZE(memoryEtalon));
EXPECTGL(glTexSubImage2D(0, 0, 4, 2, AnyOf(gl_const::GLRGBA, gl_const::GLRGBA8), gl_const::GL8BitOnChannel, _))
.WillOnce(Invoke(&cmp, &MemoryComparer::cmpSubImage));
cp.UploadResources(make_ref(&context), make_ref(&texture));
}
{
cp.MapResource(dp::ColorKey(dp::Color(0xCC, 0, 0, 0)));
cp.MapResource(dp::ColorKey(dp::Color(0xFF, 0xFF, 0, 0)));
cp.MapResource(dp::ColorKey(dp::Color(0xFF, 0xFF, 0xFF, 0)));
cp.MapResource(dp::ColorKey(dp::Color(0xFF, 0xFF, 0xFF, 0xFF)));
uint8_t memoryEtalon1[] = {
0xCC, 0x00, 0x00, 0x00, // 1 pixel
0xCC, 0x00, 0x00, 0x00, // 1 pixel
0xFF, 0xFF, 0x00, 0x00, // 2 pixel
0xFF, 0xFF, 0x00, 0x00, // 2 pixel
0xCC, 0x00, 0x00, 0x00, // 1 pixel
0xCC, 0x00, 0x00, 0x00, // 1 pixel
0xFF, 0xFF, 0x00, 0x00, // 2 pixel
0xFF, 0xFF, 0x00, 0x00, // 2 pixel
0xFF, 0xFF, 0xFF, 0x00, // 1 pixel
0xFF, 0xFF, 0xFF, 0x00, // 1 pixel
0xFF, 0xFF, 0xFF, 0xFF, // 2 pixel
0xFF, 0xFF, 0xFF, 0xFF, // 2 pixel
0xFF, 0xFF, 0xFF, 0x00, // 1 pixel
0xFF, 0xFF, 0xFF, 0x00, // 1 pixel
0xFF, 0xFF, 0xFF, 0xFF, // 2 pixel
0xFF, 0xFF, 0xFF, 0xFF, // 2 pixel
};
MemoryComparer cmp1(memoryEtalon1, ARRAY_SIZE(memoryEtalon1));
EXPECTGL(glTexSubImage2D(0, 2, 4, 4, AnyOf(gl_const::GLRGBA, gl_const::GLRGBA8), gl_const::GL8BitOnChannel, _))
.WillOnce(Invoke(&cmp1, &MemoryComparer::cmpSubImage));
cp.UploadResources(make_ref(&context), make_ref(&texture));
}
EXPECTGL(glDeleteTexture(1));
}

View file

@ -0,0 +1,157 @@
#include "testing/testing.hpp"
#include "drape/drape_global.hpp"
#include "drape/gl_gpu_program.hpp"
#include "drape/uniform_value.hpp"
#include "drape/drape_tests/gl_mock_functions.hpp"
#include <cstring>
#include <string>
#include <utility>
#include <gmock/gmock.h>
using ::testing::_;
using ::testing::AnyOf;
using ::testing::IgnoreResult;
using ::testing::InSequence;
using ::testing::Invoke;
using ::testing::Return;
using namespace dp;
namespace uniform_value_tests
{
template <typename T>
class MemoryComparer
{
public:
MemoryComparer(T const * memory, uint32_t size) : m_result(false), m_memory(memory), m_size(size) {}
void Compare(int32_t id, T const * memory) { m_result = memcmp(m_memory, memory, m_size) == 0; }
bool GetResult() const { return m_result; }
private:
bool m_result;
T const * m_memory;
uint32_t m_size;
};
void mock_glGetActiveUniform(uint32_t programID, uint32_t index, int32_t * size, glConst * type, std::string & name)
{
*size = 1;
if (index < 9)
{
static std::pair<std::string, glConst> mockUniforms[9] = {
{"position0", gl_const::GLIntType}, {"position1", gl_const::GLIntVec2},
{"position2", gl_const::GLIntVec3}, {"position3", gl_const::GLIntVec4},
{"position4", gl_const::GLFloatType}, {"position5", gl_const::GLFloatVec2},
{"position6", gl_const::GLFloatVec3}, {"position7", gl_const::GLFloatVec4},
{"viewModel", gl_const::GLFloatMat4}};
name = mockUniforms[index].first;
*type = mockUniforms[index].second;
}
else
{
ASSERT(false, ("Undefined index:", index));
}
}
UNIT_TEST(UniformValueTest)
{
uint32_t constexpr VertexShaderID = 1;
uint32_t constexpr FragmentShaderID = 2;
uint32_t constexpr ProgramID = 2;
int32_t constexpr positionLoc = 10;
int32_t constexpr modelViewLoc = 11;
float matrix[16] = {1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f};
MemoryComparer<float> comparer(matrix, 16 * sizeof(float));
{
InSequence seq;
EXPECTGL(glCreateShader(gl_const::GLVertexShader)).WillOnce(Return(VertexShaderID));
EXPECTGL(glShaderSource(VertexShaderID, _)).Times(1);
EXPECTGL(glCompileShader(VertexShaderID, _)).WillOnce(Return(true));
EXPECTGL(glCreateShader(gl_const::GLFragmentShader)).WillOnce(Return(FragmentShaderID));
EXPECTGL(glShaderSource(FragmentShaderID, _)).Times(1);
EXPECTGL(glCompileShader(FragmentShaderID, _)).WillOnce(Return(true));
EXPECTGL(glCreateProgram()).WillOnce(Return(ProgramID));
EXPECTGL(glAttachShader(ProgramID, VertexShaderID));
EXPECTGL(glAttachShader(ProgramID, FragmentShaderID));
EXPECTGL(glLinkProgram(ProgramID, _)).WillOnce(Return(true));
EXPECTGL(glGetProgramiv(ProgramID, gl_const::GLActiveUniforms)).WillOnce(Return(9));
for (int i = 0; i < 9; i++)
{
EXPECTGL(glGetActiveUniform(ProgramID, _, _, _, _)).WillOnce(Invoke(mock_glGetActiveUniform));
EXPECTGL(glGetUniformLocation(ProgramID, _)).WillOnce(Return(i == 8 ? modelViewLoc : positionLoc));
}
EXPECTGL(glDetachShader(ProgramID, VertexShaderID));
EXPECTGL(glDetachShader(ProgramID, FragmentShaderID));
EXPECTGL(glUseProgram(ProgramID));
EXPECTGL(glUniformValuei(positionLoc, 1));
EXPECTGL(glUniformValuei(positionLoc, 1, 2));
EXPECTGL(glUniformValuei(positionLoc, 1, 2, 3));
EXPECTGL(glUniformValuei(positionLoc, 1, 2, 3, 4));
EXPECTGL(glUniformValuef(positionLoc, 1.0f));
EXPECTGL(glUniformValuef(positionLoc, 1.0f, 2.0f));
EXPECTGL(glUniformValuef(positionLoc, 1.0f, 2.0f, 3.0f));
EXPECTGL(glUniformValuef(positionLoc, 1.0f, 2.0f, 3.0f, 4.0f));
EXPECTGL(glUniformMatrix4x4Value(modelViewLoc, _)).WillOnce(Invoke(&comparer, &MemoryComparer<float>::Compare));
EXPECTGL(glUseProgram(0));
EXPECTGL(glDeleteProgram(ProgramID));
EXPECTGL(glDeleteShader(AnyOf(VertexShaderID, FragmentShaderID))).Times(2);
}
drape_ptr<Shader> vs = make_unique_dp<Shader>("", "void main() { gl_Position = vec4(0.0, 0.0, 0.0, 1.0); }", "",
Shader::Type::VertexShader);
drape_ptr<Shader> fs = make_unique_dp<Shader>("", "void main() { gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); }", "",
Shader::Type::FragmentShader);
drape_ptr<GLGpuProgram> program = make_unique_dp<GLGpuProgram>("", make_ref(vs), make_ref(fs));
program->Bind();
UniformValue::ApplyRaw(positionLoc, 1);
UniformValue::ApplyRaw(positionLoc, glsl::ivec2(1, 2));
UniformValue::ApplyRaw(positionLoc, glsl::ivec3(1, 2, 3));
UniformValue::ApplyRaw(positionLoc, glsl::ivec4(1, 2, 3, 4));
UniformValue::ApplyRaw(positionLoc, 1.0f);
UniformValue::ApplyRaw(positionLoc, glsl::vec2(1.0f, 2.0f));
UniformValue::ApplyRaw(positionLoc, glsl::vec3(1.0f, 2.0f, 3.0f));
UniformValue::ApplyRaw(positionLoc, glsl::vec4(1.0f, 2.0f, 3.0f, 4.0f));
UniformValue::ApplyRaw(modelViewLoc, glsl::make_mat4(matrix));
program->Unbind();
program.reset();
vs.reset();
fs.reset();
}
} // namespace uniform_value_tests

View file

@ -0,0 +1,97 @@
#include "testing/testing.hpp"
#include "drape/utils/vertex_decl.hpp"
#include "base/buffer_vector.hpp"
#include <boost/container/small_vector.hpp>
#include <random>
#include <vector>
namespace vertex_buffer_tests
{
#ifndef DEBUG
std::vector<uint32_t> GenerateSizes(uint32_t min, uint32_t max)
{
std::vector<uint32_t> res;
std::uniform_int_distribution<uint64_t> randDist(min, max);
std::random_device randDevice;
std::mt19937 randEngine(randDevice());
for (size_t i = 0; i < 1000000; ++i)
res.push_back(randDist(randEngine));
return res;
}
UNIT_TEST(VertexBuffer_Benchmark)
{
using ValueT = gpu::TextOutlinedStaticVertex;
// using ValueT = gpu::TextDynamicVertex
ValueT objs[] = {
{{1, 1}, {2, 2}, {3, 3}},
{{4, 4}, {5, 5}, {6, 6}},
{{7, 7}, {8, 8}, {9, 9}},
};
// Very comfortable for buffer_vector<ValueT, kUpperBound>.
uint32_t constexpr kUpperBound = 128;
std::vector<uint32_t> sizes = GenerateSizes(16, kUpperBound);
uint64_t t1, t2, t3, t4;
{
base::Timer timer;
for (uint32_t sz : sizes)
{
std::vector<ValueT> v;
for (size_t i = 0; i < sz; ++i)
v.push_back(objs[i % std::size(objs)]);
}
t1 = timer.ElapsedMilliseconds();
}
{
base::Timer timer;
for (uint32_t sz : sizes)
{
buffer_vector<ValueT, kUpperBound> v;
for (size_t i = 0; i < sz; ++i)
v.push_back(objs[i % std::size(objs)]);
}
t2 = timer.ElapsedMilliseconds();
}
{
base::Timer timer;
for (uint32_t sz : sizes)
{
boost::container::small_vector<ValueT, kUpperBound> v;
for (size_t i = 0; i < sz; ++i)
v.push_back(objs[i % std::size(objs)]);
}
t3 = timer.ElapsedMilliseconds();
}
{
base::Timer timer;
for (uint32_t sz : sizes)
{
std::vector<ValueT> v;
v.reserve(sz);
for (size_t i = 0; i < sz; ++i)
v.push_back(objs[i % std::size(objs)]);
}
t4 = timer.ElapsedMilliseconds();
}
LOG(LINFO,
("vector time:", t1, "buffer_vector time:", t2, "boost::small_vector time:", t3, "reserved vector time:", t4));
TEST_LESS(t2, t1, ());
TEST_LESS(t3, t2, ());
// TODO: Fix this condition
// TEST_LESS(t4, t3, ());
}
#endif
} // namespace vertex_buffer_tests

View file

@ -0,0 +1,107 @@
#pragma once
#include "drape/texture.hpp"
#include <atomic>
#include <vector>
namespace dp
{
template <typename TIndexer, typename TResourceKey, Texture::ResourceType TResourceType>
class DynamicTexture : public Texture
{
using Base = Texture;
public:
~DynamicTexture() override { ASSERT(m_indexer == nullptr, ()); }
ref_ptr<ResourceInfo> FindResource(Key const & key, bool & newResource) override
{
ASSERT(m_indexer != nullptr, ());
if (key.GetType() != TResourceType)
return nullptr;
return m_indexer->MapResource(static_cast<TResourceKey const &>(key), newResource);
}
void Create(ref_ptr<GraphicsContext> context, Params const & params) override
{
ASSERT(Base::IsPowerOfTwo(params.m_width, params.m_height), (params.m_width, params.m_height));
Base::Create(context, params);
}
void Create(ref_ptr<GraphicsContext> context, Params const & params, ref_ptr<void> data) override
{
ASSERT(Base::IsPowerOfTwo(params.m_width, params.m_height), (params.m_width, params.m_height));
Base::Create(context, params, data);
}
void UpdateState(ref_ptr<GraphicsContext> context) override
{
// Create texture before first uploading.
if (!m_isInitialized)
{
std::vector<uint8_t> initData(m_params.m_width * m_params.m_height * GetBytesPerPixel(m_params.m_format), 0);
Create(context, m_params, initData.data());
m_isInitialized = true;
}
ASSERT(m_indexer != nullptr, ());
Bind(context);
m_indexer->UploadResources(context, make_ref(this));
}
TextureFormat GetFormat() const override { return m_params.m_format; }
uint32_t GetWidth() const override { return m_params.m_width; }
uint32_t GetHeight() const override { return m_params.m_height; }
float GetS(uint32_t x) const override { return static_cast<float>(x) / m_params.m_width; }
float GetT(uint32_t y) const override { return static_cast<float>(y) / m_params.m_height; }
uint32_t GetID() const override { return m_isInitialized ? Texture::GetID() : 0; }
void Bind(ref_ptr<dp::GraphicsContext> context) const override
{
if (m_isInitialized)
Texture::Bind(context);
}
void SetFilter(TextureFilter filter) override
{
if (m_isInitialized)
Texture::SetFilter(filter);
}
protected:
DynamicTexture() : m_isInitialized(false) {}
struct DynamicTextureParams
{
m2::PointU m_size;
dp::TextureFormat m_format = dp::TextureFormat::Unspecified;
TextureFilter m_filter = dp::TextureFilter::Nearest;
bool m_usePixelBuffer = false;
};
void Init(ref_ptr<HWTextureAllocator> allocator, ref_ptr<TIndexer> indexer, DynamicTextureParams const & params)
{
m_indexer = indexer;
m_params.m_allocator = allocator;
m_params.m_width = params.m_size.x;
m_params.m_height = params.m_size.y;
m_params.m_format = params.m_format;
m_params.m_filter = params.m_filter;
m_params.m_usePixelBuffer = params.m_usePixelBuffer;
m_params.m_isMutable = true;
}
void Reset() { m_indexer = nullptr; }
ref_ptr<TIndexer> m_indexer;
Texture::Params m_params;
std::atomic<bool> m_isInitialized;
};
} // namespace dp

View file

@ -0,0 +1,7 @@
#pragma once
namespace dp
{
int constexpr kSdfBorder = 4;
int constexpr kBaseFontSizePixels = 22;
} // namespace dp

211
libs/drape/font_texture.cpp Normal file
View file

@ -0,0 +1,211 @@
#include "drape/font_texture.hpp"
#include "drape/pointers.hpp"
#include "base/logging.hpp"
#include <algorithm>
#include <functional>
#include <iterator>
#include "font_constants.hpp"
namespace dp
{
GlyphPacker::GlyphPacker(m2::PointU const & size) : m_size(size) {}
bool GlyphPacker::PackGlyph(uint32_t width, uint32_t height, m2::RectU & rect)
{
ASSERT_LESS(width, m_size.x, ());
ASSERT_LESS(height, m_size.y, ());
if (m_cursor.x + width > m_size.x)
{
m_cursor.x = 0;
m_cursor.y += m_yStep;
m_yStep = 0;
}
if (m_cursor.y + height > m_size.y)
{
m_isFull = true;
return false;
}
rect = m2::RectU(m_cursor.x, m_cursor.y, m_cursor.x + width, m_cursor.y + height);
m_cursor.x += width;
m_yStep = std::max(height, m_yStep);
return true;
}
bool GlyphPacker::CanBePacked(uint32_t glyphsCount, uint32_t width, uint32_t height) const
{
uint32_t x = m_cursor.x;
uint32_t y = m_cursor.y;
uint32_t step = m_yStep;
for (uint32_t i = 0; i < glyphsCount; i++)
{
if (x + width > m_size.x)
{
x = 0;
y += step;
}
if (y + height > m_size.y)
return false;
x += width;
step = std::max(height, step);
}
return true;
}
m2::RectF GlyphPacker::MapTextureCoords(m2::RectU const & pixelRect) const
{
auto const width = static_cast<float>(m_size.x);
auto const height = static_cast<float>(m_size.y);
// Half-pixel offset to eliminate artefacts on fetching from texture.
float offset = 0.0f;
if (pixelRect.SizeX() != 0 && pixelRect.SizeY() != 0)
offset = 0.5f;
return {(pixelRect.minX() + offset) / width, (pixelRect.minY() + offset) / height,
(pixelRect.maxX() - offset) / width, (pixelRect.maxY() - offset) / height};
}
bool GlyphPacker::IsFull() const
{
return m_isFull;
}
GlyphIndex::GlyphIndex(m2::PointU const & size, ref_ptr<GlyphManager> mng) : m_packer(size), m_mng(mng)
{
ASSERT(m_mng != nullptr, ());
}
GlyphIndex::~GlyphIndex()
{
std::lock_guard lock(m_mutex);
for (auto & [rect, glyph] : m_pendingNodes)
glyph.m_image.Destroy();
m_pendingNodes.clear();
}
std::vector<ref_ptr<Texture::ResourceInfo>> GlyphIndex::MapResources(TGlyphs const & keys, bool & hasNewResources)
{
std::vector<ref_ptr<Texture::ResourceInfo>> info;
info.reserve(keys.size());
hasNewResources = false;
for (auto const & glyphKey : keys)
{
bool newResource = false;
auto result = MapResource(glyphKey, newResource);
hasNewResources |= newResource;
info.push_back(std::move(result));
}
return info;
}
ref_ptr<Texture::ResourceInfo> GlyphIndex::MapResource(GlyphFontAndId const & key, bool & newResource)
{
newResource = false;
if (auto const found = m_index.find(key); found != m_index.end())
return make_ref(&found->second);
newResource = true;
constexpr bool kUseSdf = true;
GlyphImage glyphImage = m_mng->GetGlyphImage(key, dp::kBaseFontSizePixels, kUseSdf);
m2::RectU r;
if (!m_packer.PackGlyph(glyphImage.m_width, glyphImage.m_height, r))
{
glyphImage.Destroy();
LOG(LWARNING, ("Glyphs packer could not pack a glyph with fontIndex =", key.m_fontIndex, "glyphId =", key.m_glyphId,
"w =", glyphImage.m_width, "h =", glyphImage.m_height, "packerSize =", m_packer.GetSize()));
// TODO(AB): Is it a valid invalid glyph?
GlyphFontAndId constexpr kInvalidGlyphKey{0, 0};
if (auto const found = m_index.find(kInvalidGlyphKey); found != m_index.end())
{
newResource = false;
return make_ref(&found->second);
}
LOG(LERROR, ("Invalid glyph was not found in the index"));
return nullptr;
}
auto res = m_index.emplace(key, GlyphInfo{m_packer.MapTextureCoords(r)});
ASSERT(res.second, ());
{
std::lock_guard lock(m_mutex);
m_pendingNodes.emplace_back(r, Glyph{std::move(glyphImage), key});
}
return make_ref(&res.first->second);
}
bool GlyphIndex::CanBeGlyphPacked(uint32_t glyphsCount) const
{
if (glyphsCount == 0)
return true;
if (m_packer.IsFull())
return false;
float constexpr kGlyphScalar = 1.5f;
auto constexpr baseSizePixels = static_cast<uint32_t>(kBaseFontSizePixels * kGlyphScalar);
return m_packer.CanBePacked(glyphsCount, baseSizePixels, baseSizePixels);
}
size_t GlyphIndex::GetPendingNodesCount()
{
std::lock_guard lock(m_mutex);
return m_pendingNodes.size();
}
void GlyphIndex::UploadResources(ref_ptr<GraphicsContext> context, ref_ptr<Texture> texture)
{
PendingNodes pendingNodes;
{
std::lock_guard lock(m_mutex);
if (m_pendingNodes.empty())
return;
m_pendingNodes.swap(pendingNodes);
}
for (auto it = pendingNodes.begin(); it != pendingNodes.end();)
{
// TODO(AB): Is it possible to mark and check if there is no image before uploading?
m_mng->MarkGlyphReady(it->second.m_key);
if (it->second.m_image.m_data)
++it;
else
it = pendingNodes.erase(it);
}
if (pendingNodes.empty())
return;
for (auto & [rect, glyph] : pendingNodes)
{
ASSERT(glyph.m_image.m_width != 0 && glyph.m_image.m_height != 0 && rect.SizeX() != 0 && rect.SizeY() != 0, ());
ASSERT_EQUAL(glyph.m_image.m_width, rect.SizeX(), ());
ASSERT_EQUAL(glyph.m_image.m_height, rect.SizeY(), ());
m2::PointU const zeroPoint = rect.LeftBottom();
uint8_t * srcMemory = SharedBufferManager::GetRawPointer(glyph.m_image.m_data);
texture->UploadData(context, zeroPoint.x, zeroPoint.y, rect.SizeX(), rect.SizeY(), make_ref(srcMemory));
glyph.m_image.Destroy();
}
}
} // namespace dp

101
libs/drape/font_texture.hpp Normal file
View file

@ -0,0 +1,101 @@
#pragma once
#include "drape/dynamic_texture.hpp"
#include "drape/glyph_manager.hpp"
#include "drape/pointers.hpp"
#include <map>
#include <mutex>
#include <utility> // std::tie
#include <vector>
namespace dp
{
class GlyphPacker
{
public:
explicit GlyphPacker(m2::PointU const & size);
bool PackGlyph(uint32_t width, uint32_t height, m2::RectU & rect);
bool CanBePacked(uint32_t glyphsCount, uint32_t width, uint32_t height) const;
m2::RectF MapTextureCoords(m2::RectU const & pixelRect) const;
bool IsFull() const;
m2::PointU const & GetSize() const { return m_size; }
private:
m2::PointU m_size = m2::PointU(0, 0);
m2::PointU m_cursor = m2::PointU(0, 0);
uint32_t m_yStep = 0;
bool m_isFull = false;
};
class GlyphKey
: public GlyphFontAndId
, public Texture::Key
{
public:
Texture::ResourceType GetType() const override { return Texture::ResourceType::Glyph; }
};
// TODO(AB): Make Texture::ResourceInfo non-abstract and use it here directly.
class GlyphInfo : public Texture::ResourceInfo
{
public:
explicit GlyphInfo(m2::RectF const & texRect) : ResourceInfo(texRect) {}
Texture::ResourceType GetType() const override { return Texture::ResourceType::Glyph; }
};
class GlyphIndex
{
public:
GlyphIndex(m2::PointU const & size, ref_ptr<GlyphManager> mng);
~GlyphIndex();
// This function can return nullptr.
ref_ptr<Texture::ResourceInfo> MapResource(GlyphFontAndId const & key, bool & newResource);
std::vector<ref_ptr<Texture::ResourceInfo>> MapResources(TGlyphs const & keys, bool & hasNewResources);
void UploadResources(ref_ptr<dp::GraphicsContext> context, ref_ptr<Texture> texture);
bool CanBeGlyphPacked(uint32_t glyphsCount) const;
// ONLY for unit-tests. DO NOT use this function anywhere else.
size_t GetPendingNodesCount();
private:
GlyphPacker m_packer;
ref_ptr<GlyphManager> m_mng;
using ResourceMapping = std::map<GlyphFontAndId, GlyphInfo>;
using PendingNode = std::pair<m2::RectU, Glyph>;
using PendingNodes = std::vector<PendingNode>;
ResourceMapping m_index;
PendingNodes m_pendingNodes;
std::mutex m_mutex;
};
class FontTexture : public DynamicTexture<GlyphIndex, GlyphKey, Texture::ResourceType::Glyph>
{
public:
FontTexture(m2::PointU const & size, ref_ptr<GlyphManager> glyphMng, ref_ptr<HWTextureAllocator> allocator)
: m_index(size, glyphMng)
{
DynamicTextureParams const params{size, TextureFormat::Red, TextureFilter::Linear, true /* m_usePixelBuffer */};
Init(allocator, make_ref(&m_index), params);
}
~FontTexture() override { Reset(); }
ref_ptr<ResourceInfo> MapResource(GlyphFontAndId const & key, bool & hasNewResources) const
{
ASSERT(m_indexer != nullptr, ());
return m_indexer->MapResource(key, hasNewResources);
}
bool HasEnoughSpace(uint32_t newKeysCount) const override { return m_index.CanBeGlyphPacked(newKeysCount); }
private:
GlyphIndex m_index;
};
} // namespace dp

View file

@ -0,0 +1,9 @@
project(fonts_tool)
set(SRC
fonts_tool.cpp
)
omim_add_executable(${PROJECT_NAME} ${SRC})
target_link_libraries(${PROJECT_NAME} drape)

View file

@ -0,0 +1,62 @@
#include "drape/glyph_manager.hpp"
#include "drape/harfbuzz_shaping.hpp"
#include "platform/platform.hpp"
#include "base/string_utils.hpp"
#include <fstream>
#include <iostream>
#include <numeric> // std::accumulate
void ItemizeLine(std::string & str, dp::GlyphManager & glyphManager)
{
strings::Trim(str);
if (str.empty())
return;
auto const segments = harfbuzz_shaping::GetTextSegments(str);
for (auto const & run : segments.m_segments)
{
std::u16string_view sv{segments.m_text.data() + run.m_start, static_cast<size_t>(run.m_length)};
std::cout << DebugPrint(sv) << '|';
}
std::cout << '\n';
}
int main(int argc, char ** argv)
{
if (argc < 2)
{
std::cout << "Debugging tool to experiment with text segmentation\n";
std::cout << "Usage: " << argv[0] << " [text file with utf8 strings or any arbitrary text string]\n";
return -1;
}
dp::GlyphManager::Params params;
params.m_uniBlocks = "unicode_blocks.txt";
params.m_whitelist = "fonts_whitelist.txt";
params.m_blacklist = "fonts_blacklist.txt";
GetPlatform().GetFontNames(params.m_fonts);
dp::GlyphManager glyphManager(params);
if (Platform::IsFileExistsByFullPath(argv[1]))
{
std::ifstream file(argv[1]);
std::string line;
while (file.good())
{
std::getline(file, line);
ItemizeLine(line, glyphManager);
}
}
else
{
// Get all args as one string.
std::vector<std::string> const args(argv + 1, argv + argc);
auto line = std::accumulate(args.begin(), args.end(), std::string{});
ItemizeLine(line, glyphManager);
}
return 0;
}

204
libs/drape/framebuffer.cpp Normal file
View file

@ -0,0 +1,204 @@
#include "drape/framebuffer.hpp"
#include "drape/gl_functions.hpp"
#include "drape/texture.hpp"
#include "base/assert.hpp"
#include "base/logging.hpp"
#include "base/string_utils.hpp"
namespace dp
{
Framebuffer::DepthStencil::DepthStencil(bool depthEnabled, bool stencilEnabled)
: m_depthEnabled(depthEnabled)
, m_stencilEnabled(stencilEnabled)
{}
Framebuffer::DepthStencil::~DepthStencil()
{
Destroy();
}
void Framebuffer::DepthStencil::SetSize(ref_ptr<dp::GraphicsContext> context, uint32_t width, uint32_t height)
{
if (m_texture && m_texture->GetWidth() == width && m_texture->GetHeight() == height)
return;
Destroy();
Texture::Params params;
params.m_width = width;
params.m_height = height;
if (m_depthEnabled && m_stencilEnabled)
params.m_format = TextureFormat::DepthStencil;
else if (m_depthEnabled)
params.m_format = TextureFormat::Depth;
else
CHECK(false, ("Unsupported depth-stencil combination."));
params.m_allocator = GetDefaultAllocator(context);
params.m_isRenderTarget = true;
m_texture = make_unique_dp<FramebufferTexture>();
m_texture->Create(context, params);
}
void Framebuffer::DepthStencil::Destroy()
{
m_texture.reset();
}
uint32_t Framebuffer::DepthStencil::GetDepthAttachmentId() const
{
ASSERT(m_texture != nullptr, ());
return m_texture->GetID();
}
uint32_t Framebuffer::DepthStencil::GetStencilAttachmentId() const
{
ASSERT(m_stencilEnabled ? m_texture != nullptr : true, ());
return m_stencilEnabled ? m_texture->GetID() : 0;
}
ref_ptr<FramebufferTexture> Framebuffer::DepthStencil::GetTexture() const
{
return make_ref(m_texture);
}
Framebuffer::Framebuffer() : m_colorFormat(TextureFormat::RGBA8)
{
ApplyOwnDepthStencil();
}
Framebuffer::Framebuffer(TextureFormat colorFormat) : m_colorFormat(colorFormat)
{
ApplyOwnDepthStencil();
}
Framebuffer::Framebuffer(TextureFormat colorFormat, bool depthEnabled, bool stencilEnabled)
: m_depthStencil(make_unique_dp<dp::Framebuffer::DepthStencil>(depthEnabled, stencilEnabled))
, m_colorFormat(colorFormat)
{
ApplyOwnDepthStencil();
}
Framebuffer::~Framebuffer()
{
Destroy();
}
void Framebuffer::Destroy()
{
m_colorTexture.reset();
if (m_depthStencil != nullptr)
m_depthStencil->Destroy();
if (m_framebufferId != 0)
{
GLFunctions::glDeleteFramebuffer(&m_framebufferId);
m_framebufferId = 0;
}
}
void Framebuffer::SetFramebufferFallback(FramebufferFallback && fallback)
{
m_framebufferFallback = std::move(fallback);
}
void Framebuffer::SetSize(ref_ptr<dp::GraphicsContext> context, uint32_t width, uint32_t height)
{
if (!m_isSupported)
return;
if (m_width == width && m_height == height)
return;
m_width = width;
m_height = height;
Destroy();
Texture::Params params;
params.m_width = width;
params.m_height = height;
params.m_format = m_colorFormat;
params.m_allocator = GetDefaultAllocator(context);
params.m_isRenderTarget = true;
m_colorTexture = make_unique_dp<FramebufferTexture>();
m_colorTexture->Create(context, params);
if (m_depthStencilRef != nullptr && m_depthStencilRef == m_depthStencil.get())
m_depthStencilRef->SetSize(context, m_width, m_height);
auto const apiVersion = context->GetApiVersion();
if (apiVersion == dp::ApiVersion::OpenGLES3)
{
glConst depthAttachmentId = 0;
glConst stencilAttachmentId = 0;
if (m_depthStencilRef != nullptr)
{
depthAttachmentId = m_depthStencilRef->GetDepthAttachmentId();
stencilAttachmentId = m_depthStencilRef->GetStencilAttachmentId();
}
GLFunctions::glGenFramebuffer(&m_framebufferId);
GLFunctions::glBindFramebuffer(m_framebufferId);
GLFunctions::glFramebufferTexture2D(gl_const::GLColorAttachment, m_colorTexture->GetID());
if (depthAttachmentId != stencilAttachmentId)
{
GLFunctions::glFramebufferTexture2D(gl_const::GLDepthAttachment, depthAttachmentId);
GLFunctions::glFramebufferTexture2D(gl_const::GLStencilAttachment, stencilAttachmentId);
}
else
{
GLFunctions::glFramebufferTexture2D(gl_const::GLDepthStencilAttachment, depthAttachmentId);
}
uint32_t const status = GLFunctions::glCheckFramebufferStatus();
if (status != gl_const::GLFramebufferComplete)
{
m_isSupported = false;
Destroy();
LOG(LWARNING, ("Framebuffer is unsupported. Framebuffer status =", status));
}
if (m_framebufferFallback != nullptr)
m_framebufferFallback();
}
}
void Framebuffer::SetDepthStencilRef(ref_ptr<DepthStencil> depthStencilRef)
{
m_depthStencilRef = std::move(depthStencilRef);
}
void Framebuffer::ApplyOwnDepthStencil()
{
m_depthStencilRef = make_ref(m_depthStencil);
}
void Framebuffer::Bind()
{
ASSERT(m_isSupported, ());
ASSERT_NOT_EQUAL(m_framebufferId, 0, ());
GLFunctions::glBindFramebuffer(m_framebufferId);
}
void Framebuffer::ApplyFallback()
{
ASSERT(m_isSupported, ());
if (m_framebufferFallback != nullptr)
m_framebufferFallback();
}
ref_ptr<Texture> Framebuffer::GetTexture() const
{
return make_ref(m_colorTexture);
}
ref_ptr<Framebuffer::DepthStencil> Framebuffer::GetDepthStencilRef() const
{
return m_depthStencilRef;
}
} // namespace dp

View file

@ -0,0 +1,72 @@
#pragma once
#include "drape/drape_global.hpp"
#include "drape/graphics_context.hpp"
#include "drape/pointers.hpp"
#include "drape/texture.hpp"
#include <cstdint>
#include <functional>
namespace dp
{
class FramebufferTexture : public Texture
{
public:
ref_ptr<ResourceInfo> FindResource(Key const & key, bool & newResource) override { return nullptr; }
};
using FramebufferFallback = std::function<bool()>;
class Framebuffer : public BaseFramebuffer
{
public:
class DepthStencil
{
public:
DepthStencil(bool depthEnabled, bool stencilEnabled);
~DepthStencil();
void SetSize(ref_ptr<dp::GraphicsContext> context, uint32_t width, uint32_t height);
void Destroy();
uint32_t GetDepthAttachmentId() const;
uint32_t GetStencilAttachmentId() const;
ref_ptr<FramebufferTexture> GetTexture() const;
private:
bool const m_depthEnabled = false;
bool const m_stencilEnabled = false;
drape_ptr<FramebufferTexture> m_texture;
};
Framebuffer();
explicit Framebuffer(TextureFormat colorFormat);
Framebuffer(TextureFormat colorFormat, bool depthEnabled, bool stencilEnabled);
~Framebuffer() override;
void SetFramebufferFallback(FramebufferFallback && fallback);
void SetSize(ref_ptr<dp::GraphicsContext> context, uint32_t width, uint32_t height);
void SetDepthStencilRef(ref_ptr<DepthStencil> depthStencilRef);
void ApplyOwnDepthStencil();
void Bind() override;
void ApplyFallback();
ref_ptr<Texture> GetTexture() const;
ref_ptr<DepthStencil> GetDepthStencilRef() const;
bool IsSupported() const { return m_isSupported; }
private:
void Destroy();
drape_ptr<DepthStencil> m_depthStencil;
ref_ptr<DepthStencil> m_depthStencilRef;
drape_ptr<FramebufferTexture> m_colorTexture;
uint32_t m_width = 0;
uint32_t m_height = 0;
uint32_t m_framebufferId = 0;
TextureFormat m_colorFormat;
FramebufferFallback m_framebufferFallback;
bool m_isSupported = true;
};
} // namespace dp

314
libs/drape/gl_constants.cpp Normal file
View file

@ -0,0 +1,314 @@
#include "drape/gl_constants.hpp"
#include "drape/gl_includes.hpp"
#if !defined(GL_RGBA8_OES)
#define GL_RGBA8_OES 0x8058
#endif
#if !defined(GL_RGBA4_OES)
#define GL_RGBA4_OES 0x8056
#endif
#if !defined(GL_ALPHA8_OES)
#define GL_ALPHA8_OES 0x803C
#endif
#if !defined(GL_LUMINANCE8_OES)
#define GL_LUMINANCE8_OES 0x8040
#endif
#if !defined(GL_LUMINANCE8_ALPHA8_OES)
#define GL_LUMINANCE8_ALPHA8_OES 0x8045
#endif
#if !defined(GL_LUMINANCE8_ALPHA4_OES)
#define GL_LUMINANCE8_ALPHA4_OES 0x8043
#endif
#if !defined(GL_LUMINANCE)
#define GL_LUMINANCE 0x1909
#endif
#if !defined(GL_LUMINANCE_ALPHA)
#define GL_LUMINANCE_ALPHA 0x190A
#endif
#if defined(GL_WRITE_ONLY)
#define WRITE_ONLY_DEF GL_WRITE_ONLY
#elif defined(GL_WRITE_ONLY_OES)
#define WRITE_ONLY_DEF GL_WRITE_ONLY_OES
#else
#define WRITE_ONLY_DEF 0x88B9
#endif
#if defined(GL_READ_ONLY)
#define READ_ONLY_DEF GL_READ_ONLY
#else
#define READ_ONLY_DEF 0x88B8
#endif
#if defined(GL_MAP_READ_BIT_EXT)
#define READ_BIT_DEF GL_MAP_READ_BIT_EXT
#else
#define READ_BIT_DEF 0x0001
#endif
#if defined(GL_MAP_WRITE_BIT_EXT)
#define WRITE_BIT_DEF GL_MAP_WRITE_BIT_EXT
#else
#define WRITE_BIT_DEF 0x0002
#endif
#if defined(GL_MAP_INVALIDATE_RANGE_BIT_EXT)
#define INVALIDATE_RANGE_BIT_DEF GL_MAP_INVALIDATE_RANGE_BIT_EXT
#else
#define INVALIDATE_RANGE_BIT_DEF 0x0004
#endif
#if defined(GL_MAP_INVALIDATE_BUFFER_BIT_EXT)
#define INVALIDATE_BUFFER_BIT_DEF GL_MAP_INVALIDATE_BUFFER_BIT_EXT
#else
#define INVALIDATE_BUFFER_BIT_DEF 0x0008
#endif
#if defined(GL_MAP_FLUSH_EXPLICIT_BIT_EXT)
#define FLUSH_EXPLICIT_BIT_DEF GL_MAP_FLUSH_EXPLICIT_BIT_EXT
#else
#define FLUSH_EXPLICIT_BIT_DEF 0x0010
#endif
#if defined(GL_MAP_UNSYNCHRONIZED_BIT_EXT)
#define UNSYNCHRONIZED_BIT_DEF GL_MAP_UNSYNCHRONIZED_BIT_EXT
#else
#define UNSYNCHRONIZED_BIT_DEF 0x0020
#endif
#if !defined(GL_FUNC_ADD)
#define GL_FUNC_ADD 0x8006
#endif
#if !defined(GL_FUNC_SUBTRACT)
#define GL_FUNC_SUBTRACT 0x800A
#endif
#if !defined(GL_FUNC_REVERSE_SUBTRACT)
#define GL_FUNC_REVERSE_SUBTRACT 0x800B
#endif
namespace gl_const
{
constexpr glConst GLUnpackAlignment = GL_UNPACK_ALIGNMENT;
constexpr glConst GLRenderer = GL_RENDERER;
constexpr glConst GLVendor = GL_VENDOR;
constexpr glConst GLVersion = GL_VERSION;
#ifdef GL_VERSION_3_0
constexpr glConst glContextFlags = GL_CONTEXT_FLAGS;
#else
constexpr glConst glContextFlags = 0;
#endif
constexpr glConst GLColorBit = GL_COLOR_BUFFER_BIT;
constexpr glConst GLDepthBit = GL_DEPTH_BUFFER_BIT;
constexpr glConst GLStencilBit = GL_STENCIL_BUFFER_BIT;
constexpr glConst GLMaxFragmentTextures = GL_MAX_TEXTURE_IMAGE_UNITS;
constexpr glConst GLMaxVertexTextures = GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS;
constexpr glConst GLMaxTextureSize = GL_MAX_TEXTURE_SIZE;
constexpr glConst GLArrayBuffer = GL_ARRAY_BUFFER;
constexpr glConst GLElementArrayBuffer = GL_ELEMENT_ARRAY_BUFFER;
constexpr glConst GLPixelBufferWrite = GL_PIXEL_UNPACK_BUFFER;
constexpr glConst GLBufferSize = GL_BUFFER_SIZE;
constexpr glConst GLBufferUsage = GL_BUFFER_USAGE;
constexpr glConst GLWriteOnly = WRITE_ONLY_DEF;
constexpr glConst GLReadOnly = READ_ONLY_DEF;
constexpr glConst GLReadBufferBit = READ_BIT_DEF;
constexpr glConst GLWriteBufferBit = WRITE_BIT_DEF;
constexpr glConst GLInvalidateRange = INVALIDATE_RANGE_BIT_DEF;
constexpr glConst GLInvalidateBuffer = INVALIDATE_BUFFER_BIT_DEF;
constexpr glConst GLFlushExplicit = FLUSH_EXPLICIT_BIT_DEF;
constexpr glConst GLUnsynchronized = UNSYNCHRONIZED_BIT_DEF;
constexpr glConst GLStaticDraw = GL_STATIC_DRAW;
constexpr glConst GLStreamDraw = GL_STREAM_DRAW;
constexpr glConst GLDynamicDraw = GL_DYNAMIC_DRAW;
constexpr glConst GLVertexShader = GL_VERTEX_SHADER;
constexpr glConst GLFragmentShader = GL_FRAGMENT_SHADER;
constexpr glConst GLCurrentProgram = GL_CURRENT_PROGRAM;
constexpr glConst GLRGBA = GL_RGBA;
constexpr glConst GLRGB = GL_RGB;
constexpr glConst GLAlpha = GL_ALPHA;
constexpr glConst GLLuminance = GL_LUMINANCE;
constexpr glConst GLAlphaLuminance = GL_LUMINANCE_ALPHA;
constexpr glConst GLDepthComponent = GL_DEPTH_COMPONENT;
constexpr glConst GLDepthStencil = GL_DEPTH_STENCIL;
constexpr glConst GLRGBA8 = GL_RGBA8_OES;
constexpr glConst GLRGBA4 = GL_RGBA4_OES;
constexpr glConst GLAlpha8 = GL_ALPHA8_OES;
constexpr glConst GLLuminance8 = GL_LUMINANCE8_OES;
constexpr glConst GLAlphaLuminance8 = GL_LUMINANCE8_ALPHA8_OES;
constexpr glConst GLAlphaLuminance4 = GL_LUMINANCE8_ALPHA4_OES;
constexpr glConst GLRed = GL_RED;
constexpr glConst GLRedGreen = GL_RG;
constexpr glConst GL8BitOnChannel = GL_UNSIGNED_BYTE;
constexpr glConst GL4BitOnChannel = GL_UNSIGNED_SHORT_4_4_4_4;
constexpr glConst GLTexture2D = GL_TEXTURE_2D;
constexpr glConst GLTexture0 = GL_TEXTURE0;
constexpr glConst GLMinFilter = GL_TEXTURE_MIN_FILTER;
constexpr glConst GLMagFilter = GL_TEXTURE_MAG_FILTER;
constexpr glConst GLWrapS = GL_TEXTURE_WRAP_S;
constexpr glConst GLWrapT = GL_TEXTURE_WRAP_T;
constexpr glConst GLRepeat = GL_REPEAT;
constexpr glConst GLMirroredRepeat = GL_MIRRORED_REPEAT;
constexpr glConst GLClampToEdge = GL_CLAMP_TO_EDGE;
constexpr glConst GLLinear = GL_LINEAR;
constexpr glConst GLNearest = GL_NEAREST;
constexpr glConst GLByteType = GL_BYTE;
constexpr glConst GLUnsignedByteType = GL_UNSIGNED_BYTE;
constexpr glConst GLShortType = GL_SHORT;
constexpr glConst GLUnsignedShortType = GL_UNSIGNED_SHORT;
constexpr glConst GLIntType = GL_INT;
constexpr glConst GLUnsignedIntType = GL_UNSIGNED_INT;
constexpr glConst GLFloatType = GL_FLOAT;
constexpr glConst GLUnsignedInt24_8Type = GL_UNSIGNED_INT_24_8;
constexpr glConst GLFloatVec2 = GL_FLOAT_VEC2;
constexpr glConst GLFloatVec3 = GL_FLOAT_VEC3;
constexpr glConst GLFloatVec4 = GL_FLOAT_VEC4;
constexpr glConst GLIntVec2 = GL_INT_VEC2;
constexpr glConst GLIntVec3 = GL_INT_VEC3;
constexpr glConst GLIntVec4 = GL_INT_VEC4;
constexpr glConst GLFloatMat4 = GL_FLOAT_MAT4;
constexpr glConst GLSampler2D = GL_SAMPLER_2D;
constexpr glConst GLAddBlend = GL_FUNC_ADD;
constexpr glConst GLSubstractBlend = GL_FUNC_SUBTRACT;
constexpr glConst GLReverseSubstrBlend = GL_FUNC_REVERSE_SUBTRACT;
constexpr glConst GLZero = GL_ZERO;
constexpr glConst GLOne = GL_ONE;
constexpr glConst GLSrcColor = GL_SRC_COLOR;
constexpr glConst GLOneMinusSrcColor = GL_ONE_MINUS_SRC_COLOR;
constexpr glConst GLDstColor = GL_DST_COLOR;
constexpr glConst GLOneMinusDstColor = GL_ONE_MINUS_DST_COLOR;
constexpr glConst GLSrcAlpha = GL_SRC_ALPHA;
constexpr glConst GLOneMinusSrcAlpha = GL_ONE_MINUS_SRC_ALPHA;
constexpr glConst GLDstAlpha = GL_DST_ALPHA;
constexpr glConst GLOneMinusDstAlpha = GL_ONE_MINUS_DST_ALPHA;
constexpr glConst GLDepthTest = GL_DEPTH_TEST;
constexpr glConst GLBlending = GL_BLEND;
constexpr glConst GLCullFace = GL_CULL_FACE;
constexpr glConst GLScissorTest = GL_SCISSOR_TEST;
constexpr glConst GLStencilTest = GL_STENCIL_TEST;
constexpr glConst GLDontCare = GL_DONT_CARE;
constexpr glConst GLTrue = GL_TRUE;
constexpr glConst GLFalse = GL_FALSE;
#ifdef GL_VERSION_4_3
constexpr glConst GLDebugOutput = GL_DEBUG_OUTPUT;
constexpr glConst GLDebugOutputSynchronous = GL_DEBUG_OUTPUT_SYNCHRONOUS;
constexpr glConst GLDebugSourceApi = GL_DEBUG_SOURCE_API;
constexpr glConst GLDebugSourceShaderCompiler = GL_DEBUG_SOURCE_SHADER_COMPILER;
constexpr glConst GLDebugSourceThirdParty = GL_DEBUG_SOURCE_THIRD_PARTY;
constexpr glConst GLDebugSourceApplication = GL_DEBUG_SOURCE_APPLICATION;
constexpr glConst GLDebugSourceOther = GL_DEBUG_SOURCE_OTHER;
constexpr glConst GLDebugTypeError = GL_DEBUG_TYPE_ERROR;
constexpr glConst GLDebugDeprecatedBehavior = GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR;
constexpr glConst GLDebugUndefinedBehavior = GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR;
constexpr glConst GLDebugPortability = GL_DEBUG_TYPE_PORTABILITY;
constexpr glConst GLDebugPerformance = GL_DEBUG_TYPE_PERFORMANCE;
constexpr glConst GLDebugOther = GL_DEBUG_TYPE_OTHER;
constexpr glConst GLDebugSeverityLow = GL_DEBUG_SEVERITY_LOW;
constexpr glConst GLDebugSeverityMedium = GL_DEBUG_SEVERITY_MEDIUM;
constexpr glConst GLDebugSeverityHigh = GL_DEBUG_SEVERITY_HIGH;
constexpr glConst GLDebugSeverityNotification = GL_DEBUG_SEVERITY_NOTIFICATION;
constexpr glConst glContextFlagDebugBit = GL_CONTEXT_FLAG_DEBUG_BIT;
#else
constexpr glConst GLDebugOutput = 0;
constexpr glConst GLDebugOutputSynchronous = 0;
constexpr glConst GLDebugSourceApi = 0;
constexpr glConst GLDebugSourceShaderCompiler = 0;
constexpr glConst GLDebugSourceThirdParty = 0;
constexpr glConst GLDebugSourceApplication = 0;
constexpr glConst GLDebugSourceOther = 0;
constexpr glConst GLDebugTypeError = 0;
constexpr glConst GLDebugDeprecatedBehavior = 0;
constexpr glConst GLDebugUndefinedBehavior = 0;
constexpr glConst GLDebugPortability = 0;
constexpr glConst GLDebugPerformance = 0;
constexpr glConst GLDebugOther = 0;
constexpr glConst GLDebugSeverityLow = 0;
constexpr glConst GLDebugSeverityMedium = 0;
constexpr glConst GLDebugSeverityHigh = 0;
constexpr glConst GLDebugSeverityNotification = 0;
constexpr glConst glContextFlagDebugBit = 0;
#endif
constexpr glConst GLClockwise = GL_CW;
constexpr glConst GLCounterClockwise = GL_CCW;
constexpr glConst GLFront = GL_FRONT;
constexpr glConst GLBack = GL_BACK;
constexpr glConst GLFrontAndBack = GL_FRONT_AND_BACK;
constexpr glConst GLNever = GL_NEVER;
constexpr glConst GLLess = GL_LESS;
constexpr glConst GLEqual = GL_EQUAL;
constexpr glConst GLLessOrEqual = GL_LEQUAL;
constexpr glConst GLGreat = GL_GREATER;
constexpr glConst GLNotEqual = GL_NOTEQUAL;
constexpr glConst GLGreatOrEqual = GL_GEQUAL;
constexpr glConst GLAlways = GL_ALWAYS;
constexpr glConst GLKeep = GL_KEEP;
constexpr glConst GLIncr = GL_INCR;
constexpr glConst GLDecr = GL_DECR;
constexpr glConst GLInvert = GL_INVERT;
constexpr glConst GLReplace = GL_REPLACE;
constexpr glConst GLIncrWrap = GL_INCR_WRAP;
constexpr glConst GLDecrWrap = GL_DECR_WRAP;
constexpr glConst GLActiveUniforms = GL_ACTIVE_UNIFORMS;
constexpr glConst GLLines = GL_LINES;
constexpr glConst GLLineStrip = GL_LINE_STRIP;
constexpr glConst GLTriangles = GL_TRIANGLES;
constexpr glConst GLTriangleStrip = GL_TRIANGLE_STRIP;
constexpr glConst GLColorAttachment = GL_COLOR_ATTACHMENT0;
constexpr glConst GLDepthAttachment = GL_DEPTH_ATTACHMENT;
constexpr glConst GLStencilAttachment = GL_STENCIL_ATTACHMENT;
constexpr glConst GLDepthStencilAttachment = GL_DEPTH_STENCIL_ATTACHMENT;
constexpr glConst GLFramebufferComplete = GL_FRAMEBUFFER_COMPLETE;
} // namespace gl_const

222
libs/drape/gl_constants.hpp Normal file
View file

@ -0,0 +1,222 @@
#pragma once
#include <cstdint>
using glConst = uint32_t;
namespace gl_const
{
extern glConst const GLUnpackAlignment;
extern glConst const GLRenderer;
extern glConst const GLVendor;
extern glConst const GLVersion;
extern glConst const glContextFlags;
extern glConst const glContextFlagDebugBit;
/// Clear bits
extern glConst const GLColorBit;
extern glConst const GLDepthBit;
extern glConst const GLStencilBit;
/// Hardware specific params
extern glConst const GLMaxFragmentTextures;
extern glConst const GLMaxVertexTextures;
extern glConst const GLMaxTextureSize;
/// Buffer targets
extern glConst const GLArrayBuffer;
extern glConst const GLElementArrayBuffer;
extern glConst const GLPixelBufferWrite;
/// Buffer params
extern glConst const GLBufferSize;
extern glConst const GLBufferUsage;
/// VBO Access
extern glConst const GLWriteOnly;
extern glConst const GLReadOnly;
/// MapBufferRange
extern glConst const GLWriteBufferBit;
extern glConst const GLReadBufferBit;
extern glConst const GLInvalidateRange;
extern glConst const GLInvalidateBuffer;
extern glConst const GLFlushExplicit;
extern glConst const GLUnsynchronized;
/// BufferUsage
extern glConst const GLStaticDraw;
extern glConst const GLStreamDraw;
extern glConst const GLDynamicDraw;
/// ShaderType
extern glConst const GLVertexShader;
extern glConst const GLFragmentShader;
extern glConst const GLCurrentProgram;
/// Texture layouts
extern glConst const GLRGBA;
extern glConst const GLRGB;
extern glConst const GLAlpha;
extern glConst const GLLuminance;
extern glConst const GLAlphaLuminance;
extern glConst const GLDepthComponent;
extern glConst const GLDepthStencil;
/// Texture layout size
extern glConst const GLRGBA8;
extern glConst const GLRGBA4;
extern glConst const GLAlpha8;
extern glConst const GLLuminance8;
extern glConst const GLAlphaLuminance8;
extern glConst const GLAlphaLuminance4;
extern glConst const GLRed;
extern glConst const GLRedGreen;
/// Pixel type for texture upload
extern glConst const GL8BitOnChannel;
extern glConst const GL4BitOnChannel;
/// Texture targets
extern glConst const GLTexture2D;
/// Texture uniform blocks
extern glConst const GLTexture0;
/// Texture param names
extern glConst const GLMinFilter;
extern glConst const GLMagFilter;
extern glConst const GLWrapS;
extern glConst const GLWrapT;
/// Texture Wrap Modes
extern glConst const GLRepeat;
extern glConst const GLMirroredRepeat;
extern glConst const GLClampToEdge;
/// Texture Filter Modes
extern glConst const GLLinear;
extern glConst const GLNearest;
/// OpenGL types
extern glConst const GLByteType;
extern glConst const GLUnsignedByteType;
extern glConst const GLShortType;
extern glConst const GLUnsignedShortType;
extern glConst const GLIntType;
extern glConst const GLUnsignedIntType;
extern glConst const GLFloatType;
extern glConst const GLUnsignedInt24_8Type;
extern glConst const GLFloatVec2;
extern glConst const GLFloatVec3;
extern glConst const GLFloatVec4;
extern glConst const GLIntVec2;
extern glConst const GLIntVec3;
extern glConst const GLIntVec4;
extern glConst const GLFloatMat4;
extern glConst const GLSampler2D;
/// Blend Functions
extern glConst const GLAddBlend;
extern glConst const GLSubstractBlend;
extern glConst const GLReverseSubstrBlend;
/// Blend Factors
extern glConst const GLZero;
extern glConst const GLOne;
extern glConst const GLSrcColor;
extern glConst const GLOneMinusSrcColor;
extern glConst const GLDstColor;
extern glConst const GLOneMinusDstColor;
extern glConst const GLSrcAlpha;
extern glConst const GLOneMinusSrcAlpha;
extern glConst const GLDstAlpha;
extern glConst const GLOneMinusDstAlpha;
/// OpenGL states
extern glConst const GLDepthTest;
extern glConst const GLBlending;
extern glConst const GLCullFace;
extern glConst const GLScissorTest;
extern glConst const GLStencilTest;
extern glConst const GLDebugOutput;
extern glConst const GLDebugOutputSynchronous;
extern glConst const GLDontCare;
extern glConst const GLDontCare;
extern glConst const GLTrue;
extern glConst const GLFalse;
// OpenGL source type
extern glConst const GLDebugSourceApi;
extern glConst const GLDebugSourceShaderCompiler;
extern glConst const GLDebugSourceThirdParty;
extern glConst const GLDebugSourceApplication;
extern glConst const GLDebugSourceOther;
// OpenGL debug type
extern glConst const GLDebugTypeError;
extern glConst const GLDebugDeprecatedBehavior;
extern glConst const GLDebugUndefinedBehavior;
extern glConst const GLDebugPortability;
extern glConst const GLDebugPerformance;
extern glConst const GLDebugOther;
// OpenGL debug severity
extern glConst const GLDebugSeverityLow;
extern glConst const GLDebugSeverityMedium;
extern glConst const GLDebugSeverityHigh;
extern glConst const GLDebugSeverityNotification;
/// Triangle faces order
extern glConst const GLClockwise;
extern glConst const GLCounterClockwise;
/// Triangle face
extern glConst const GLFront;
extern glConst const GLBack;
extern glConst const GLFrontAndBack;
/// OpenGL depth functions
extern glConst const GLNever;
extern glConst const GLLess;
extern glConst const GLEqual;
extern glConst const GLLessOrEqual;
extern glConst const GLGreat;
extern glConst const GLNotEqual;
extern glConst const GLGreatOrEqual;
extern glConst const GLAlways;
/// OpenGL stencil functions
extern glConst const GLKeep;
extern glConst const GLIncr;
extern glConst const GLDecr;
extern glConst const GLInvert;
extern glConst const GLReplace;
extern glConst const GLIncrWrap;
extern glConst const GLDecrWrap;
/// Program object parameter names
extern glConst const GLActiveUniforms;
/// Draw primitives
extern glConst const GLLines;
extern glConst const GLLineStrip;
extern glConst const GLTriangles;
extern glConst const GLTriangleStrip;
/// Framebuffer attachment points
extern glConst const GLColorAttachment;
extern glConst const GLDepthAttachment;
extern glConst const GLStencilAttachment;
extern glConst const GLDepthStencilAttachment;
/// Framebuffer status
extern glConst const GLFramebufferComplete;
} // namespace gl_const

View file

@ -0,0 +1,40 @@
#include "drape/gl_extensions_list.hpp"
#include "drape/gl_functions.hpp"
#include "base/assert.hpp"
#include "std/target_os.hpp"
namespace dp
{
void GLExtensionsList::Init()
{
#ifdef OMIM_OS_ANDROID
// NOTE: MapBuffer/MapBufferRange are disabled by performance reasons according to
// https://github.com/organicmaps/organicmaps/commit/d72ab7c8cd8be0eb5a622d9d33ae943b391d5707
SetExtension(MapBuffer, false);
#else
SetExtension(MapBuffer, true);
#endif
}
bool GLExtensionsList::IsSupported(ExtensionName extName) const
{
auto const it = m_supportedMap.find(extName);
if (it != m_supportedMap.end())
return it->second;
ASSERT(false, ("Not all used extensions are checked"));
return false;
}
void GLExtensionsList::CheckExtension(ExtensionName enumName, std::string const & extName)
{
m_supportedMap[enumName] = GLFunctions::glHasExtension(extName);
}
void GLExtensionsList::SetExtension(ExtensionName enumName, bool isSupported)
{
m_supportedMap[enumName] = isSupported;
}
} // namespace dp

View file

@ -0,0 +1,32 @@
#pragma once
#include "drape/drape_global.hpp"
#include "base/macros.hpp"
#include <map>
#include <string>
namespace dp
{
class GLExtensionsList
{
public:
enum ExtensionName
{
MapBuffer,
};
GLExtensionsList() = default;
void Init();
bool IsSupported(ExtensionName extName) const;
private:
void CheckExtension(ExtensionName enumName, std::string const & extName);
void SetExtension(ExtensionName enumName, bool isSupported);
std::map<ExtensionName, bool> m_supportedMap;
DISALLOW_COPY_AND_MOVE(GLExtensionsList);
};
} // namespace dp

1070
libs/drape/gl_functions.cpp Normal file

File diff suppressed because it is too large Load diff

177
libs/drape/gl_functions.hpp Normal file
View file

@ -0,0 +1,177 @@
#pragma once
#include "drape/drape_global.hpp"
#include "drape/gl_constants.hpp"
#include "drape/gl_extensions_list.hpp"
#include "base/src_point.hpp"
#include <string>
#include <thread>
class GLFunctions
{
public:
static dp::ApiVersion CurrentApiVersion;
static dp::GLExtensionsList ExtensionsList;
static void Init(dp::ApiVersion apiVersion);
static bool glHasExtension(std::string const & name);
static void glClearColor(float r, float g, float b, float a);
static void glClear(uint32_t clearBits);
static void glViewport(uint32_t x, uint32_t y, uint32_t w, uint32_t h);
static void glScissor(uint32_t x, uint32_t y, uint32_t w, uint32_t h);
static void glFlush();
static void glFinish();
static void glFrontFace(glConst mode);
static void glCullFace(glConst face);
static void glStencilOpSeparate(glConst face, glConst sfail, glConst dpfail, glConst dppass);
static void glStencilFuncSeparate(glConst face, glConst func, int ref, uint32_t mask);
static void glPixelStore(glConst name, uint32_t value);
static int32_t glGetInteger(glConst pname);
/// target = { gl_const::GLArrayBuffer, gl_const::GLElementArrayBuffer }
/// name = { gl_const::GLBufferSize, gl_const::GLBufferUsage }
static int32_t glGetBufferParameter(glConst target, glConst name);
static std::string glGetString(glConst pname);
static int32_t glGetMaxLineWidth();
static void glEnable(glConst mode);
static void glDisable(glConst mode);
static void glClearDepthValue(double depth);
static void glDepthMask(bool needWriteToDepthBuffer);
static void glDepthFunc(glConst depthFunc);
static void glBlendEquation(glConst function);
static void glBlendFunc(glConst srcFactor, glConst dstFactor);
/// Debug Output
static bool CanEnableDebugMessages();
using TglDebugProc = void (*)(glConst source, glConst type, uint32_t id, glConst severity, int32_t length,
char const * message, void * userParam);
static void glDebugMessageCallback(TglDebugProc messageCallback, void * userParam);
static void glDebugMessageControl(glConst source, glConst type, glConst severity, int32_t count, uint32_t const * ids,
uint8_t enabled);
static void glLineWidth(uint32_t value);
/// VAO support
static uint32_t glGenVertexArray();
static void glBindVertexArray(uint32_t vao);
static void glDeleteVertexArray(uint32_t vao);
/// VBO support
static uint32_t glGenBuffer();
/// target - type of buffer to bind. Look GLConst
static void glBindBuffer(uint32_t vbo, glConst target);
static void glDeleteBuffer(uint32_t vbo);
/// usage - Look GLConst
static void glBufferData(glConst target, uint32_t size, void const * data, glConst usage);
static void glBufferSubData(glConst target, uint32_t size, void const * data, uint32_t offset);
static void * glMapBuffer(glConst target, glConst access = gl_const::GLWriteOnly);
static void glUnmapBuffer(glConst target);
static void * glMapBufferRange(glConst target, uint32_t offset, uint32_t length, glConst access);
static void glFlushMappedBufferRange(glConst target, uint32_t offset, uint32_t length);
/// Shaders support
static uint32_t glCreateShader(glConst type);
static void glShaderSource(uint32_t shaderID, std::string const & src, std::string const & defines);
static bool glCompileShader(uint32_t shaderID, std::string & errorLog);
static void glDeleteShader(uint32_t shaderID);
static uint32_t glCreateProgram();
static void glAttachShader(uint32_t programID, uint32_t shaderID);
static void glDetachShader(uint32_t programID, uint32_t shaderID);
static bool glLinkProgram(uint32_t programID, std::string & errorLog);
static void glDeleteProgram(uint32_t programID);
static void glUseProgram(uint32_t programID);
static int8_t glGetAttribLocation(uint32_t programID, std::string const & name);
static void glBindAttribLocation(uint32_t programID, uint8_t index, std::string const & name);
/// enable vertex attribute binding. To get attributeLocation need to call glGetAttributeLocation
static void glEnableVertexAttribute(int32_t attributeLocation);
/// Configure vertex attribute binding.
/// attrLocation - attribute location in shader program
/// count - specify number of components with "type" for generic attribute
/// needNormalize - if "true" then OGL will map integer value on [-1 : 1] for signed of on [0 : 1]
/// for unsigned
/// if "false" it will direct convert to float type
/// if "type" == GLFloat this parameter have no sense
/// stride - how much bytes need to seek from current attribute value to get the second value
/// offset - how much bytes need to seek from begin of currenct buffer to get first attribute
/// value
static void glVertexAttributePointer(int32_t attrLocation, uint32_t count, glConst type, bool needNormalize,
uint32_t stride, uint32_t offset);
static void glGetActiveUniform(uint32_t programID, uint32_t uniformIndex, int32_t * uniformSize, glConst * type,
std::string & name);
static int8_t glGetUniformLocation(uint32_t programID, std::string const & name);
static void glUniformValuei(int8_t location, int32_t v);
static void glUniformValuei(int8_t location, int32_t v1, int32_t v2);
static void glUniformValuei(int8_t location, int32_t v1, int32_t v2, int32_t v3);
static void glUniformValuei(int8_t location, int32_t v1, int32_t v2, int32_t v3, int32_t v4);
static void glUniformValueiv(int8_t location, int32_t * v, uint32_t size);
static void glUniformValuef(int8_t location, float v);
static void glUniformValuef(int8_t location, float v1, float v2);
static void glUniformValuef(int8_t location, float v1, float v2, float v3);
static void glUniformValuef(int8_t location, float v1, float v2, float v3, float v4);
static void glUniformValuefv(int8_t location, float * v, uint32_t size);
static void glUniformMatrix4x4Value(int8_t location, float const * values);
static uint32_t glGetCurrentProgram();
static int32_t glGetProgramiv(uint32_t program, glConst paramName);
// Textures support
static void glActiveTexture(glConst texBlock);
static uint32_t glGenTexture();
static void glDeleteTexture(uint32_t id);
static void glBindTexture(uint32_t textureID);
static void glTexImage2D(int width, int height, glConst layout, glConst pixelType, void const * data);
static void glTexSubImage2D(int x, int y, int width, int height, glConst layout, glConst pixelType,
void const * data);
static void glTexParameter(glConst param, glConst value);
// Draw support
static void glDrawElements(glConst primitive, uint32_t sizeOfIndex, uint32_t indexCount, uint32_t startIndex = 0);
static void glDrawArrays(glConst mode, int32_t first, uint32_t count);
// FBO support
static void glGenFramebuffer(uint32_t * fbo);
static void glDeleteFramebuffer(uint32_t * fbo);
static void glBindFramebuffer(uint32_t fbo);
static void glFramebufferTexture2D(glConst attachment, glConst texture);
static uint32_t glCheckFramebufferStatus();
};
void CheckGLError(base::SrcPoint const & src);
#ifdef DEBUG
#define GLCHECK(x) \
do \
{ \
(x); \
CheckGLError(SRC()); \
} \
while (false)
#define GLCHECKCALL() \
do \
{ \
CheckGLError(SRC()); \
} \
while (false)
#else
#define GLCHECK(x) (x)
#define GLCHECKCALL()
#endif

View file

@ -0,0 +1,115 @@
#include "drape/gl_gpu_program.hpp"
#include "drape/gl_functions.hpp"
#include "drape/render_state.hpp"
#include "drape/support_manager.hpp"
#include "base/logging.hpp"
#include <set>
namespace dp
{
GLGpuProgram::GLGpuProgram(std::string const & programName, ref_ptr<Shader> vertexShader,
ref_ptr<Shader> fragmentShader)
: GpuProgram(programName)
, m_vertexShader(vertexShader)
, m_fragmentShader(fragmentShader)
{
m_programID = GLFunctions::glCreateProgram();
GLFunctions::glAttachShader(m_programID, m_vertexShader->GetID());
GLFunctions::glAttachShader(m_programID, m_fragmentShader->GetID());
std::string errorLog;
if (!GLFunctions::glLinkProgram(m_programID, errorLog))
LOG(LERROR, ("Program ", programName, " link error = ", errorLog));
LoadUniformLocations();
GLFunctions::glDetachShader(m_programID, m_vertexShader->GetID());
GLFunctions::glDetachShader(m_programID, m_fragmentShader->GetID());
}
GLGpuProgram::~GLGpuProgram()
{
GLFunctions::glDeleteProgram(m_programID);
}
void GLGpuProgram::Bind()
{
// Deactivate all unused textures.
uint8_t const usedSlots = TextureState::GetLastUsedSlots();
for (uint8_t i = m_textureSlotsCount; i < usedSlots; i++)
{
GLFunctions::glActiveTexture(gl_const::GLTexture0 + i);
GLFunctions::glBindTexture(0);
}
GLFunctions::glUseProgram(m_programID);
}
void GLGpuProgram::Unbind()
{
GLFunctions::glUseProgram(0);
}
int8_t GLGpuProgram::GetAttributeLocation(std::string const & attributeName) const
{
return GLFunctions::glGetAttribLocation(m_programID, attributeName);
}
int8_t GLGpuProgram::GetUniformLocation(std::string const & uniformName) const
{
auto const it = m_uniforms.find(uniformName);
if (it == m_uniforms.end())
return -1;
return it->second.m_location;
}
glConst GLGpuProgram::GetUniformType(std::string const & uniformName) const
{
auto const it = m_uniforms.find(uniformName);
if (it == m_uniforms.end())
return -1;
return it->second.m_type;
}
GLGpuProgram::UniformsInfo const & GLGpuProgram::GetUniformsInfo() const
{
return m_uniforms;
}
void GLGpuProgram::LoadUniformLocations()
{
static std::set<glConst> const kSupportedTypes = {gl_const::GLFloatType, gl_const::GLFloatVec2, gl_const::GLFloatVec3,
gl_const::GLFloatVec4, gl_const::GLIntType, gl_const::GLIntVec2,
gl_const::GLIntVec3, gl_const::GLIntVec4, gl_const::GLFloatMat4,
gl_const::GLSampler2D};
auto const uniformsCount = GLFunctions::glGetProgramiv(m_programID, gl_const::GLActiveUniforms);
for (int i = 0; i < uniformsCount; ++i)
{
int32_t size = 0;
UniformInfo info;
std::string name;
GLFunctions::glGetActiveUniform(m_programID, static_cast<uint32_t>(i), &size, &info.m_type, name);
CHECK(kSupportedTypes.find(info.m_type) != kSupportedTypes.cend(),
("Used uniform has unsupported type. Program =", m_programName, "; Type =", info.m_type, "; Name =", name));
info.m_location = GLFunctions::glGetUniformLocation(m_programID, name);
m_uniforms[name] = std::move(info);
}
m_numericUniformsCount = CalculateNumericUniformsCount();
m_textureSlotsCount = static_cast<uint8_t>(m_uniforms.size() - m_numericUniformsCount);
}
uint32_t GLGpuProgram::CalculateNumericUniformsCount() const
{
uint32_t counter = 0;
for (auto const & u : m_uniforms)
if (u.second.m_type != gl_const::GLSampler2D)
counter++;
return counter;
}
} // namespace dp

View file

@ -0,0 +1,50 @@
#pragma once
#include "drape/gl_constants.hpp"
#include "drape/gpu_program.hpp"
#include "drape/pointers.hpp"
#include "drape/shader.hpp"
#include <map>
#include <string>
#include <vector>
namespace dp
{
class GLGpuProgram : public GpuProgram
{
public:
GLGpuProgram(std::string const & programName, ref_ptr<Shader> vertexShader, ref_ptr<Shader> fragmentShader);
~GLGpuProgram() override;
void Bind() override;
void Unbind() override;
int8_t GetAttributeLocation(std::string const & attributeName) const;
int8_t GetUniformLocation(std::string const & uniformName) const;
glConst GetUniformType(std::string const & uniformName) const;
struct UniformInfo
{
int8_t m_location = -1;
glConst m_type = gl_const::GLFloatType;
};
using UniformsInfo = std::map<std::string, UniformInfo>;
UniformsInfo const & GetUniformsInfo() const;
uint32_t GetNumericUniformsCount() const { return m_numericUniformsCount; }
private:
void LoadUniformLocations();
uint32_t CalculateNumericUniformsCount() const;
uint32_t m_programID;
ref_ptr<Shader> m_vertexShader;
ref_ptr<Shader> m_fragmentShader;
UniformsInfo m_uniforms;
uint8_t m_textureSlotsCount = 0;
uint32_t m_numericUniformsCount = 0;
};
} // namespace dp

View file

@ -0,0 +1,33 @@
#pragma once
#include "std/target_os.hpp"
#if defined(OMIM_OS_IPHONE)
#define GL_SILENCE_DEPRECATION
#include <OpenGLES/ES2/glext.h>
#include <OpenGLES/ES3/gl.h>
#elif defined(OMIM_OS_MAC)
#define GL_SILENCE_DEPRECATION
#include <OpenGL/gl3.h>
#include <OpenGL/glext.h>
#elif defined(OMIM_OS_WINDOWS)
#include "std/windows.hpp"
#define GL_GLEXT_PROTOTYPES
#include <GL/gl.h>
#include "3party/GL/glext.h"
#elif defined(OMIM_OS_ANDROID)
#define GL_GLEXT_PROTOTYPES
#include <EGL/egl.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include "android/sdk/src/main/cpp/app/organicmaps/sdk/opengl/gl3stub.h"
#elif defined(OMIM_OS_LINUX)
#define GL_GLEXT_PROTOTYPES
#include <GL/gl.h>
#include <GL/glext.h>
#include <dlfcn.h>
#else
#define GL_GLEXT_PROTOTYPES
#include <GL/gl.h>
#include <GL/glext.h>
#endif

20
libs/drape/glsl_func.hpp Normal file
View file

@ -0,0 +1,20 @@
#pragma once
#include <glm/geometric.hpp>
#include <glm/gtc/matrix_transform.hpp>
#define GLM_ENABLE_EXPERIMENTAL // TODO: Remove this line after upgrading glm to the latest version.
#include "std/glm_gtx_rotate_vector.hpp"
namespace glsl
{
using glm::cross;
using glm::distance;
using glm::dot;
using glm::length;
using glm::normalize;
using glm::rotate;
using glm::scale;
using glm::translate;
using glm::transpose;
} // namespace glsl

119
libs/drape/glsl_types.hpp Normal file
View file

@ -0,0 +1,119 @@
#pragma once
#include "drape/color.hpp"
#include "geometry/point2d.hpp"
#include <glm/gtc/quaternion.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm/mat3x3.hpp>
#include <glm/mat4x2.hpp>
#include <glm/mat4x3.hpp>
#include <glm/mat4x4.hpp>
#include <glm/vec2.hpp>
#include <glm/vec3.hpp>
#include <glm/vec4.hpp>
#include <type_traits>
namespace glsl
{
using glm::vec2;
using glm::vec3;
using glm::vec4;
using glm::dvec2;
using glm::dvec3;
using glm::dvec4;
using glm::ivec2;
using glm::ivec3;
using glm::ivec4;
using glm::uvec2;
using glm::uvec3;
using glm::uvec4;
using glm::mat3;
using glm::mat4;
using glm::mat4x2;
using glm::mat4x3;
using glm::dmat3;
using glm::dmat4;
using glm::dmat4x2;
using glm::dmat4x3;
using glm::value_ptr;
using glm::make_mat4;
using glm::make_vec2;
using glm::make_vec3;
using glm::make_vec4;
inline m2::PointF ToPoint(vec2 const & v)
{
return m2::PointF(v.x, v.y);
}
inline vec2 ToVec2(m2::PointF const & pt)
{
return glsl::vec2(pt.x, pt.y);
}
inline vec2 ToVec2(m2::PointD const & pt)
{
return glsl::vec2(pt.x, pt.y);
}
inline m2::PointD FromVec2(glsl::vec2 const & pt)
{
return m2::PointD(pt.x, pt.y);
}
inline vec3 ToVec3(dp::Color const & color)
{
return glsl::vec3(static_cast<float>(color.GetRed()) / 255, static_cast<float>(color.GetGreen()) / 255,
static_cast<float>(color.GetBlue()) / 255);
}
inline vec4 ToVec4(dp::Color const & color)
{
return glsl::vec4(static_cast<float>(color.GetRed()) / 255, static_cast<float>(color.GetGreen()) / 255,
static_cast<float>(color.GetBlue()) / 255, static_cast<float>(color.GetAlpha()) / 255);
}
inline vec4 ToVec4(m2::PointD const & pt1, m2::PointD const & pt2)
{
return glsl::vec4(pt1.x, pt1.y, pt2.x, pt2.y);
}
template <typename T, typename = std::enable_if_t<std::is_integral<T>::value || std::is_floating_point<T>::value>>
inline uint8_t GetArithmeticComponentCount()
{
return 1;
}
template <typename T>
inline uint8_t GetComponentCount()
{
return GetArithmeticComponentCount<T>();
}
template <>
inline uint8_t GetComponentCount<vec2>()
{
return 2;
}
template <>
inline uint8_t GetComponentCount<vec3>()
{
return 3;
}
template <>
inline uint8_t GetComponentCount<vec4>()
{
return 4;
}
} // namespace glsl

62
libs/drape/glyph.hpp Normal file
View file

@ -0,0 +1,62 @@
#pragma once
#include "base/assert.hpp"
#include "base/buffer_vector.hpp"
#include "base/shared_buffer_manager.hpp"
#include <tuple> // std::tie
namespace dp
{
struct GlyphImage
{
~GlyphImage() { ASSERT_NOT_EQUAL(m_data.use_count(), 1, ("Probably you forgot to call Destroy()")); }
// TODO(AB): Get rid of manual call to Destroy.
void Destroy()
{
if (m_data != nullptr)
{
SharedBufferManager::instance().freeSharedBuffer(m_data->size(), m_data);
m_data = nullptr;
}
}
uint32_t m_width;
uint32_t m_height;
SharedBufferManager::shared_buffer_ptr_t m_data;
};
struct GlyphFontAndId
{
int16_t m_fontIndex;
uint16_t m_glyphId;
// Required only for buffer_vector's internal T m_static[N];
GlyphFontAndId() = default;
constexpr GlyphFontAndId(int16_t fontIndex, uint16_t glyphId) : m_fontIndex(fontIndex), m_glyphId(glyphId) {}
bool operator==(GlyphFontAndId const & other) const
{
return m_fontIndex == other.m_fontIndex && m_glyphId == other.m_glyphId;
}
bool operator<(GlyphFontAndId const & other) const
{
return std::tie(m_fontIndex, m_glyphId) < std::tie(other.m_fontIndex, other.m_glyphId);
}
};
// 50 glyphs should fit most of the strings based on tests in Switzerland and China.
using TGlyphs = buffer_vector<GlyphFontAndId, 50>;
struct Glyph
{
Glyph(GlyphImage && image, GlyphFontAndId key) : m_image(image), m_key(key) {}
GlyphImage m_image;
GlyphFontAndId m_key;
};
} // namespace dp

View file

@ -0,0 +1,655 @@
#include "drape/glyph_manager.hpp"
#include "drape/font_constants.hpp"
#include "drape/glyph.hpp"
#include "drape/harfbuzz_shaping.hpp"
#include "platform/platform.hpp"
#include "coding/hex.hpp"
#include "coding/reader.hpp"
#include "coding/string_utf8_multilang.hpp"
#include "base/internal/message.hpp"
#include "base/logging.hpp"
#include "base/macros.hpp"
#include "base/stl_helpers.hpp"
#include "base/string_utils.hpp"
#include <ft2build.h>
#include <hb-ft.h>
#include <unicode/unistr.h>
#include FT_FREETYPE_H
#include FT_MODULE_H
#include FT_SYSTEM_H
#include FT_SIZES_H
#include FT_TYPES_H
#undef __FTERRORS_H__
#define FT_ERRORDEF(e, v, s) {e, s},
#define FT_ERROR_START_LIST {
#define FT_ERROR_END_LIST {0, 0}} \
;
struct FreetypeError
{
int m_code;
char const * const m_message;
};
FreetypeError constexpr g_FT_Errors[] =
#include FT_ERRORS_H
#ifdef DEBUG
#define FREETYPE_CHECK(x) \
do \
{ \
FT_Error const err = (x); \
if (err) \
LOG(LERROR, ("Freetype:", g_FT_Errors[err].m_code, g_FT_Errors[err].m_message)); \
} \
while (false)
#else
#define FREETYPE_CHECK(x) x
#endif
namespace dp
{
int constexpr kInvalidFont = -1;
template <typename ToDo>
void ParseUniBlocks(std::string const & uniBlocksFile, ToDo toDo)
{
std::string uniBlocks;
try
{
ReaderPtr<Reader>(GetPlatform().GetReader(uniBlocksFile)).ReadAsString(uniBlocks);
}
catch (RootException const & e)
{
LOG(LCRITICAL, ("Error reading uniblock description: ", e.what()));
return;
}
std::istringstream fin(uniBlocks);
while (true)
{
std::string name;
uint32_t start, end;
fin >> name >> std::hex >> start >> std::hex >> end;
if (!fin)
break;
toDo(name, start, end);
}
}
template <typename ToDo>
void ParseFontList(std::string const & fontListFile, ToDo toDo)
{
std::string fontList;
try
{
ReaderPtr<Reader>(GetPlatform().GetReader(fontListFile)).ReadAsString(fontList);
}
catch (RootException const & e)
{
LOG(LWARNING, ("Error reading font list ", fontListFile, " : ", e.what()));
return;
}
std::istringstream fin(fontList);
while (true)
{
std::string ubName;
std::string fontName;
fin >> ubName >> fontName;
if (!fin)
break;
toDo(ubName, fontName);
}
}
class Font
{
public:
DECLARE_EXCEPTION(InvalidFontException, RootException);
DISALLOW_COPY_AND_MOVE(Font);
Font(ReaderPtr<Reader> && fontReader, FT_Library lib) : m_fontReader(std::move(fontReader)), m_fontFace(nullptr)
{
std::memset(&m_stream, 0, sizeof(m_stream));
m_stream.size = static_cast<unsigned long>(m_fontReader.Size());
m_stream.descriptor.pointer = &m_fontReader;
m_stream.read = &Font::Read;
m_stream.close = &Font::Close;
FT_Open_Args args = {};
args.flags = FT_OPEN_STREAM;
args.stream = &m_stream;
FT_Error const err = FT_Open_Face(lib, &args, 0, &m_fontFace);
if (err || !IsValid())
MYTHROW(InvalidFontException, (g_FT_Errors[err].m_code, g_FT_Errors[err].m_message));
// The same font size is used to render all glyphs to textures and to shape them.
FT_Set_Pixel_Sizes(m_fontFace, kBaseFontSizePixels, kBaseFontSizePixels);
FT_Activate_Size(m_fontFace->size);
}
~Font()
{
ASSERT(m_fontFace, ());
if (m_harfbuzzFont)
hb_font_destroy(m_harfbuzzFont);
FREETYPE_CHECK(FT_Done_Face(m_fontFace));
}
bool IsValid() const { return m_fontFace && m_fontFace->num_glyphs > 0; }
bool HasGlyph(strings::UniChar unicodePoint) const { return FT_Get_Char_Index(m_fontFace, unicodePoint) != 0; }
GlyphImage GetGlyphImage(uint16_t glyphId, int pixelHeight, bool sdf) const
{
FREETYPE_CHECK(FT_Set_Pixel_Sizes(m_fontFace, 0, pixelHeight));
// FT_LOAD_RENDER with FT_RENDER_MODE_SDF uses bsdf driver that is around 3x times faster
// than sdf driver, activated by FT_LOAD_DEFAULT + FT_RENDER_MODE_SDF
FREETYPE_CHECK(FT_Load_Glyph(m_fontFace, glyphId, FT_LOAD_RENDER));
if (sdf)
FREETYPE_CHECK(FT_Render_Glyph(m_fontFace->glyph, FT_RENDER_MODE_SDF));
FT_Bitmap const & bitmap = m_fontFace->glyph->bitmap;
SharedBufferManager::shared_buffer_ptr_t data;
if (bitmap.buffer != nullptr)
{
// Bitmap is stored without a padding.
data = SharedBufferManager::instance().reserveSharedBuffer(bitmap.width * bitmap.rows);
auto ptr = data->data();
for (unsigned int row = 0; row < bitmap.rows; ++row)
{
unsigned int const dstBaseIndex = row * bitmap.width;
int const srcBaseIndex = static_cast<int>(row) * bitmap.pitch;
for (unsigned int column = 0; column < bitmap.width; ++column)
ptr[dstBaseIndex + column] = bitmap.buffer[srcBaseIndex + column];
}
}
return {bitmap.width, bitmap.rows, data};
}
void GetCharcodes(std::vector<FT_ULong> & charcodes) const
{
FT_UInt gindex;
charcodes.push_back(FT_Get_First_Char(m_fontFace, &gindex));
while (gindex)
charcodes.push_back(FT_Get_Next_Char(m_fontFace, charcodes.back(), &gindex));
base::SortUnique(charcodes);
}
static unsigned long Read(FT_Stream stream, unsigned long offset, unsigned char * buffer, unsigned long count)
{
if (count != 0)
{
auto * reader = reinterpret_cast<ReaderPtr<Reader> *>(stream->descriptor.pointer);
reader->Read(offset, buffer, count);
}
return count;
}
static void Close(FT_Stream) {}
void MarkGlyphReady(uint16_t glyphId) { m_readyGlyphs.emplace(glyphId); }
bool IsGlyphReady(uint16_t glyphId) const { return m_readyGlyphs.find(glyphId) != m_readyGlyphs.end(); }
std::string GetName() const { return std::string(m_fontFace->family_name) + ':' + m_fontFace->style_name; }
// This code is not thread safe.
void Shape(hb_buffer_t * hbBuffer, int fontPixelSize, int fontIndex, text::TextMetrics & outMetrics)
{
// TODO(AB): Do not set the same font size every time.
// TODO(AB): Use hb_font_set_scale to scale the same size font in HB instead of changing it in Freetype.
FREETYPE_CHECK(FT_Set_Pixel_Sizes(m_fontFace, 0 /* pixel_width */, fontPixelSize /* pixel_height */));
if (!m_harfbuzzFont)
m_harfbuzzFont = hb_ft_font_create(m_fontFace, nullptr);
// else
// Call on each font size change.
// hb_ft_font_changed(m_harfbuzzFont);
// Shape!
hb_shape(m_harfbuzzFont, hbBuffer, nullptr, 0);
// Get the glyph and position information.
unsigned int glyphCount;
hb_glyph_info_t const * glyphInfo = hb_buffer_get_glyph_infos(hbBuffer, &glyphCount);
hb_glyph_position_t const * glyphPos = hb_buffer_get_glyph_positions(hbBuffer, &glyphCount);
for (unsigned int i = 0; i < glyphCount; ++i)
{
// TODO(AB): Check for missing glyph ID?
auto const glyphId = static_cast<uint16_t>(glyphInfo[i].codepoint);
FT_Int32 constexpr flags = FT_LOAD_DEFAULT;
FREETYPE_CHECK(FT_Load_Glyph(m_fontFace, glyphId, flags));
auto const & currPos = glyphPos[i];
auto const & metrics = m_fontFace->glyph->metrics;
auto const xOffset = static_cast<int32_t>((currPos.x_offset + metrics.horiBearingX) >> 6);
// The original Drape code expects a bottom, not a top offset in its calculations.
auto const yOffset = static_cast<int32_t>((currPos.y_offset + metrics.horiBearingY - metrics.height) >> 6);
int32_t const xAdvance = currPos.x_advance >> 6;
// yAdvance is always zero for horizontal text layouts.
outMetrics.AddGlyphMetrics(static_cast<int16_t>(fontIndex), glyphId, xOffset, yOffset, xAdvance, fontPixelSize);
}
}
private:
ReaderPtr<Reader> m_fontReader;
FT_StreamRec_ m_stream;
FT_Face m_fontFace;
std::set<uint16_t> m_readyGlyphs;
hb_font_t * m_harfbuzzFont{nullptr};
};
// Information about single unicode block.
struct UnicodeBlock
{
std::string m_name;
strings::UniChar m_start;
strings::UniChar m_end;
std::vector<int> m_fontsWeight;
UnicodeBlock(std::string const & name, strings::UniChar start, strings::UniChar end)
: m_name(name)
, m_start(start)
, m_end(end)
{}
int GetFontOffset(int idx) const
{
if (m_fontsWeight.empty())
return kInvalidFont;
int maxWeight = 0;
int upperBoundWeight = std::numeric_limits<int>::max();
if (idx != kInvalidFont)
upperBoundWeight = m_fontsWeight[idx];
int index = kInvalidFont;
ASSERT_LESS(m_fontsWeight.size(), static_cast<size_t>(std::numeric_limits<int>::max()), ());
for (size_t i = 0; i < m_fontsWeight.size(); ++i)
{
int const w = m_fontsWeight[i];
if (w < upperBoundWeight && w > maxWeight)
{
maxWeight = w;
index = static_cast<int>(i);
}
}
return index;
}
bool HasSymbol(strings::UniChar sym) const { return (m_start <= sym) && (m_end >= sym); }
};
using TUniBlocks = std::vector<UnicodeBlock>;
using TUniBlockIter = TUniBlocks::const_iterator;
struct GlyphManager::Impl
{
DISALLOW_COPY_AND_MOVE(Impl);
Impl() { m_harfbuzzBuffer = hb_buffer_create(); }
~Impl()
{
m_fonts.clear();
if (m_library)
FREETYPE_CHECK(FT_Done_FreeType(m_library));
hb_buffer_destroy(m_harfbuzzBuffer);
}
FT_Library m_library;
TUniBlocks m_blocks;
TUniBlockIter m_lastUsedBlock;
std::vector<std::unique_ptr<Font>> m_fonts;
// Required to use std::string_view as a search key for std::unordered_map::find().
struct StringHash : public std::hash<std::string_view>
{
using is_transparent = void;
};
// TODO(AB): Compare performance with std::map.
std::unordered_map<std::string, text::TextMetrics, StringHash, std::equal_to<>> m_textMetricsCache;
hb_buffer_t * m_harfbuzzBuffer;
};
// Destructor is defined where pimpl's destructor is already known.
GlyphManager::~GlyphManager() = default;
GlyphManager::GlyphManager(Params const & params) : m_impl(std::make_unique<Impl>())
{
using TFontAndBlockName = std::pair<std::string, std::string>;
using TFontLst = buffer_vector<TFontAndBlockName, 64>;
TFontLst whitelst;
TFontLst blacklst;
m_impl->m_blocks.reserve(160);
ParseUniBlocks(params.m_uniBlocks, [this](std::string const & name, strings::UniChar start, strings::UniChar end)
{ m_impl->m_blocks.emplace_back(name, start, end); });
ParseFontList(params.m_whitelist, [&whitelst](std::string const & ubName, std::string const & fontName)
{ whitelst.emplace_back(fontName, ubName); });
ParseFontList(params.m_blacklist, [&blacklst](std::string const & ubName, std::string const & fontName)
{ blacklst.emplace_back(fontName, ubName); });
m_impl->m_fonts.reserve(params.m_fonts.size());
FREETYPE_CHECK(FT_Init_FreeType(&m_impl->m_library));
// Default Freetype spread/sdf border is 8.
static constexpr FT_Int kSdfBorder = dp::kSdfBorder;
for (auto const module : {"sdf", "bsdf"})
FREETYPE_CHECK(FT_Property_Set(m_impl->m_library, module, "spread", &kSdfBorder));
for (auto const & fontName : params.m_fonts)
{
bool ignoreFont = false;
std::for_each(blacklst.begin(), blacklst.end(), [&ignoreFont, &fontName](TFontAndBlockName const & p)
{
if (p.first == fontName && p.second == "*")
ignoreFont = true;
});
if (ignoreFont)
continue;
std::vector<FT_ULong> charCodes;
try
{
m_impl->m_fonts.emplace_back(std::make_unique<Font>(GetPlatform().GetReader(fontName), m_impl->m_library));
m_impl->m_fonts.back()->GetCharcodes(charCodes);
}
catch (RootException const & e)
{
LOG(LWARNING, ("Error reading font file =", fontName, "; Reason =", e.what()));
continue;
}
using BlockIndex = size_t;
using CharCounter = int;
using CoverNode = std::pair<BlockIndex, CharCounter>;
using CoverInfo = std::vector<CoverNode>;
size_t currentUniBlock = 0;
CoverInfo coverInfo;
for (auto const charCode : charCodes)
{
size_t block = currentUniBlock;
while (block < m_impl->m_blocks.size())
{
if (m_impl->m_blocks[block].HasSymbol(static_cast<strings::UniChar>(charCode)))
break;
++block;
}
if (block < m_impl->m_blocks.size())
{
if (coverInfo.empty() || coverInfo.back().first != block)
coverInfo.emplace_back(block, 1);
else
++coverInfo.back().second;
currentUniBlock = block;
}
}
using TUpdateCoverInfoFn = std::function<void(UnicodeBlock const & uniBlock, CoverNode & node)>;
auto const enumerateFn = [this, &coverInfo, &fontName](TFontLst const & lst, TUpdateCoverInfoFn const & fn)
{
for (auto const & b : lst)
{
if (b.first != fontName)
continue;
for (CoverNode & node : coverInfo)
{
auto const & uniBlock = m_impl->m_blocks[node.first];
if (uniBlock.m_name == b.second)
{
fn(uniBlock, node);
break;
}
else if (b.second == "*")
{
fn(uniBlock, node);
}
}
}
};
enumerateFn(blacklst, [](UnicodeBlock const &, CoverNode & node) { node.second = 0; });
enumerateFn(whitelst, [this](UnicodeBlock const & uniBlock, CoverNode & node)
{ node.second = static_cast<int>(uniBlock.m_end + 1 - uniBlock.m_start + m_impl->m_fonts.size()); });
for (CoverNode const & node : coverInfo)
{
UnicodeBlock & uniBlock = m_impl->m_blocks[node.first];
uniBlock.m_fontsWeight.resize(m_impl->m_fonts.size(), 0);
uniBlock.m_fontsWeight.back() = node.second;
}
}
m_impl->m_lastUsedBlock = m_impl->m_blocks.end();
LOG(LDEBUG, ("How unicode blocks are mapped on font files:"));
// We don't have black list for now.
ASSERT_EQUAL(m_impl->m_fonts.size(), params.m_fonts.size(), ());
for (auto const & b : m_impl->m_blocks)
{
auto const & weights = b.m_fontsWeight;
ASSERT_LESS_OR_EQUAL(weights.size(), m_impl->m_fonts.size(), ());
if (weights.empty())
{
LOG_SHORT(LDEBUG, (b.m_name, "is unsupported"));
}
else
{
size_t const ind = std::distance(weights.begin(), std::max_element(weights.begin(), weights.end()));
LOG_SHORT(LDEBUG, (b.m_name, "is in", params.m_fonts[ind]));
}
}
}
int GlyphManager::GetFontIndex(strings::UniChar unicodePoint)
{
auto iter = m_impl->m_blocks.cend();
if (m_impl->m_lastUsedBlock != m_impl->m_blocks.end() && m_impl->m_lastUsedBlock->HasSymbol(unicodePoint))
{
iter = m_impl->m_lastUsedBlock;
}
else if (iter == m_impl->m_blocks.end() || !iter->HasSymbol(unicodePoint))
{
iter = std::lower_bound(m_impl->m_blocks.begin(), m_impl->m_blocks.end(), unicodePoint,
[](UnicodeBlock const & block, strings::UniChar const & v) { return block.m_end < v; });
}
if (iter == m_impl->m_blocks.end() || !iter->HasSymbol(unicodePoint))
return kInvalidFont;
m_impl->m_lastUsedBlock = iter;
return FindFontIndexInBlock(*m_impl->m_lastUsedBlock, unicodePoint);
}
int GlyphManager::GetFontIndex(std::u16string_view sv)
{
// Only get font for the first character.
// TODO(AB): Make sure that text runs are split by fonts.
auto it = sv.begin();
return GetFontIndex(utf8::unchecked::next16(it));
}
int GlyphManager::GetFontIndexImmutable(strings::UniChar unicodePoint) const
{
TUniBlockIter iter =
std::lower_bound(m_impl->m_blocks.begin(), m_impl->m_blocks.end(), unicodePoint,
[](UnicodeBlock const & block, strings::UniChar const & v) { return block.m_end < v; });
if (iter == m_impl->m_blocks.end() || !iter->HasSymbol(unicodePoint))
return kInvalidFont;
return FindFontIndexInBlock(*iter, unicodePoint);
}
int GlyphManager::FindFontIndexInBlock(UnicodeBlock const & block, strings::UniChar unicodePoint) const
{
ASSERT(block.HasSymbol(unicodePoint), ());
for (int fontIndex = block.GetFontOffset(kInvalidFont); fontIndex != kInvalidFont;
fontIndex = block.GetFontOffset(fontIndex))
{
ASSERT_LESS(fontIndex, static_cast<int>(m_impl->m_fonts.size()), ());
auto const & f = m_impl->m_fonts[fontIndex];
if (f->HasGlyph(unicodePoint))
return fontIndex;
}
return kInvalidFont;
}
void GlyphManager::MarkGlyphReady(GlyphFontAndId key)
{
ASSERT_GREATER_OR_EQUAL(key.m_fontIndex, 0, ());
ASSERT_LESS(key.m_fontIndex, static_cast<int>(m_impl->m_fonts.size()), ());
m_impl->m_fonts[key.m_fontIndex]->MarkGlyphReady(key.m_glyphId);
}
bool GlyphManager::AreGlyphsReady(TGlyphs const & glyphs) const
{
for (auto [fontIndex, glyphId] : glyphs)
{
ASSERT_NOT_EQUAL(fontIndex, kInvalidFont, ());
if (!m_impl->m_fonts[fontIndex]->IsGlyphReady(glyphId))
return false;
}
return true;
}
// TODO(AB): Check and support invalid glyphs.
GlyphImage GlyphManager::GetGlyphImage(GlyphFontAndId key, int pixelHeight, bool sdf) const
{
return m_impl->m_fonts[key.m_fontIndex]->GetGlyphImage(key.m_glyphId, pixelHeight, sdf);
}
namespace
{
hb_language_t OrganicMapsLanguageToHarfbuzzLanguage(int8_t lang)
{
// TODO(AB): can langs be converted faster?
auto const svLang = StringUtf8Multilang::GetLangByCode(lang);
auto const hbLanguage = hb_language_from_string(svLang.data(), static_cast<int>(svLang.size()));
if (hbLanguage == HB_LANGUAGE_INVALID)
return hb_language_get_default();
return hbLanguage;
}
} // namespace
// This method is NOT multithreading-safe.
text::TextMetrics GlyphManager::ShapeText(std::string_view utf8, int fontPixelHeight, int8_t lang)
{
#ifdef DEBUG
static int const fontSize = fontPixelHeight;
ASSERT_EQUAL(fontSize, fontPixelHeight,
("Cache relies on the same font height/metrics for each glyph", fontSize, fontPixelHeight));
#endif
// A simple cache greatly speeds up text metrics calculation. It has 80+% hit ratio in most scenarios.
if (auto const found = m_impl->m_textMetricsCache.find(utf8); found != m_impl->m_textMetricsCache.end())
return found->second;
auto const [text, segments] = harfbuzz_shaping::GetTextSegments(utf8);
// TODO(AB): Optimize language conversion.
hb_language_t const hbLanguage = OrganicMapsLanguageToHarfbuzzLanguage(lang);
text::TextMetrics allGlyphs;
// For SplitText it's enough to know if the last visual (first logical) segment is RTL.
allGlyphs.m_isRTL = segments.back().m_direction == HB_DIRECTION_RTL;
allGlyphs.m_glyphs.reserve(strings::CountChar(utf8));
for (auto const & substring : segments)
{
hb_buffer_clear_contents(m_impl->m_harfbuzzBuffer);
// TODO(AB): Some substrings use different fonts.
hb_buffer_add_utf16(m_impl->m_harfbuzzBuffer, reinterpret_cast<uint16_t const *>(text.data()),
static_cast<int>(text.size()), substring.m_start, substring.m_length);
hb_buffer_set_direction(m_impl->m_harfbuzzBuffer, substring.m_direction);
hb_buffer_set_script(m_impl->m_harfbuzzBuffer, substring.m_script);
hb_buffer_set_language(m_impl->m_harfbuzzBuffer, hbLanguage);
auto u32CharacterIter{text.begin() + substring.m_start};
auto const end{u32CharacterIter + substring.m_length};
do
{
auto const u32Character = utf8::unchecked::next16(u32CharacterIter);
// TODO(AB): Mapping characters to fonts can be optimized.
int const fontIndex = GetFontIndex(u32Character);
if (fontIndex < 0)
LOG(LWARNING, ("No font was found for character", NumToHex(u32Character)));
else
{
// TODO(AB): Mapping font only by the first character in a string may fail in theory in some cases.
m_impl->m_fonts[fontIndex]->Shape(m_impl->m_harfbuzzBuffer, fontPixelHeight, fontIndex, allGlyphs);
break;
}
}
while (u32CharacterIter != end);
}
// Uncomment utf8 printing for debugging if necessary. It crashes JNI with non-modified UTF-8 strings on Android 5
// and 6. See https://github.com/organicmaps/organicmaps/issues/10685
if (allGlyphs.m_glyphs.empty())
LOG(LWARNING, ("No glyphs were found in all fonts for string with characters in warnings above" /*, utf8*/));
// Empirically measured, may need more tuning.
size_t constexpr kMaxCacheSize = 50000;
if (m_impl->m_textMetricsCache.size() > kMaxCacheSize)
{
LOG(LINFO, ("Clearing text metrics cache"));
// TODO(AB): Is there a better way? E.g. clear a half of the cache?
m_impl->m_textMetricsCache.clear();
}
m_impl->m_textMetricsCache.emplace(utf8, allGlyphs);
return allGlyphs;
}
text::TextMetrics GlyphManager::ShapeText(std::string_view utf8, int fontPixelHeight, char const * lang)
{
return ShapeText(utf8, fontPixelHeight, StringUtf8Multilang::GetLangIndex(lang));
}
} // namespace dp

View file

@ -0,0 +1,89 @@
#pragma once
#include "drape/glyph.hpp"
#include "base/string_utils.hpp"
#include <string>
#include <vector>
namespace dp
{
struct UnicodeBlock;
// TODO(AB): Move to a separate file?
namespace text
{
struct GlyphMetrics
{
GlyphFontAndId m_key;
// TODO(AB): Store original font units or floats?
int32_t m_xOffset;
int32_t m_yOffset;
int32_t m_xAdvance;
// yAdvance is used only in vertical text layouts and is 0 for horizontal texts.
int32_t m_yAdvance{0};
bool operator==(GlyphMetrics const & other) const { return m_key == other.m_key; }
};
// TODO(AB): Move to a separate file?
struct TextMetrics
{
int32_t m_lineWidthInPixels{0};
int32_t m_maxLineHeightInPixels{0};
std::vector<GlyphMetrics> m_glyphs;
// Used for SplitText.
bool m_isRTL{false};
void AddGlyphMetrics(int16_t font, uint16_t glyphId, int32_t xOffset, int32_t yOffset, int32_t xAdvance,
int32_t height)
{
m_glyphs.push_back({{font, glyphId}, xOffset, yOffset, xAdvance});
if (m_glyphs.size() == 1)
xAdvance -= xOffset;
// if (yOffset > 0)
// height += yOffset; // TODO(AB): Is it needed? Is it correct?
m_lineWidthInPixels += xAdvance;
m_maxLineHeightInPixels = std::max(m_maxLineHeightInPixels, height);
}
};
} // namespace text
class GlyphManager
{
public:
struct Params
{
std::string m_uniBlocks;
std::string m_whitelist;
std::string m_blacklist;
std::vector<std::string> m_fonts;
};
explicit GlyphManager(Params const & params);
~GlyphManager();
void MarkGlyphReady(GlyphFontAndId key);
bool AreGlyphsReady(TGlyphs const & str) const;
int GetFontIndex(strings::UniChar unicodePoint);
int GetFontIndex(std::u16string_view sv);
text::TextMetrics ShapeText(std::string_view utf8, int fontPixelHeight, int8_t lang);
text::TextMetrics ShapeText(std::string_view utf8, int fontPixelHeight, char const * lang);
GlyphImage GetGlyphImage(GlyphFontAndId key, int pixelHeight, bool sdf) const;
private:
// Immutable version can be called from any thread and doesn't require internal synchronization.
int GetFontIndexImmutable(strings::UniChar unicodePoint) const;
int FindFontIndexInBlock(UnicodeBlock const & block, strings::UniChar unicodePoint) const;
struct Impl;
std::unique_ptr<Impl> m_impl;
};
} // namespace dp

162
libs/drape/gpu_buffer.cpp Normal file
View file

@ -0,0 +1,162 @@
#include "drape/gpu_buffer.hpp"
#include "drape/drape_diagnostics.hpp"
#include "drape/gl_functions.hpp"
#include "drape/utils/gpu_mem_tracker.hpp"
#include "base/assert.hpp"
#include <cstring>
namespace dp
{
namespace
{
bool IsMapBufferSupported()
{
static bool const isSupported = GLFunctions::ExtensionsList.IsSupported(GLExtensionsList::MapBuffer);
return isSupported;
}
glConst glTarget(GPUBuffer::Target t)
{
if (t == GPUBuffer::ElementBuffer)
return gl_const::GLArrayBuffer;
return gl_const::GLElementArrayBuffer;
}
} // namespace
GPUBuffer::GPUBuffer(Target t, void const * data, uint8_t elementSize, uint32_t capacity, uint64_t batcherHash)
: TBase(elementSize, capacity)
, m_t(t)
, m_mappingOffset(0)
#ifdef TRACK_GPU_MEM
, m_batcherHash(batcherHash)
#endif
#ifdef DEBUG
, m_isMapped(false)
#endif
{
UNUSED_VALUE(batcherHash);
m_bufferID = GLFunctions::glGenBuffer();
Resize(data, capacity);
}
GPUBuffer::~GPUBuffer()
{
GLFunctions::glBindBuffer(0, glTarget(m_t));
GLFunctions::glDeleteBuffer(m_bufferID);
#if defined(TRACK_GPU_MEM)
dp::GPUMemTracker::Inst().RemoveDeallocated("VBO", m_bufferID);
#endif
}
void GPUBuffer::UploadData(void const * data, uint32_t elementCount)
{
ASSERT(!m_isMapped, ());
uint32_t currentSize = GetCurrentSize();
uint8_t elementSize = GetElementSize();
ASSERT(GetCapacity() >= elementCount + currentSize, ("Not enough memory to upload ", elementCount, " elements"));
Bind();
#if defined(CHECK_VBO_BOUNDS)
int32_t size = GLFunctions::glGetBufferParameter(glTarget(m_t), gl_const::GLBufferSize);
ASSERT_EQUAL(GetCapacity() * elementSize, size, ());
ASSERT_LESS_OR_EQUAL((elementCount + currentSize) * elementSize, size, ());
#endif
GLFunctions::glBufferSubData(glTarget(m_t), elementCount * elementSize, data, currentSize * elementSize);
TBase::UploadData(elementCount);
#if defined(TRACK_GPU_MEM)
dp::GPUMemTracker::Inst().SetUsed("VBO", m_bufferID, (currentSize + elementCount) * elementSize);
#endif
}
void GPUBuffer::Bind()
{
GLFunctions::glBindBuffer(m_bufferID, glTarget(m_t));
}
void * GPUBuffer::Map(uint32_t elementOffset, uint32_t elementCount)
{
#ifdef DEBUG
ASSERT(!m_isMapped, ());
m_isMapped = true;
#endif
if (!IsMapBufferSupported())
{
m_mappingOffset = elementOffset;
return nullptr;
}
m_mappingOffset = 0;
uint32_t const elementSize = GetElementSize();
uint32_t const byteOffset = elementOffset * elementSize;
uint32_t const byteCount = elementCount * elementSize;
return GLFunctions::glMapBufferRange(glTarget(m_t), byteOffset, byteCount, gl_const::GLWriteBufferBit);
}
void GPUBuffer::UpdateData(void * gpuPtr, void const * data, uint32_t elementOffset, uint32_t elementCount)
{
uint32_t const elementSize = GetElementSize();
uint32_t const byteOffset = (elementOffset + m_mappingOffset) * elementSize;
uint32_t const byteCount = elementCount * elementSize;
uint32_t const byteCapacity = GetCapacity() * elementSize;
ASSERT(m_isMapped, ());
#if defined(CHECK_VBO_BOUNDS)
int32_t size = GLFunctions::glGetBufferParameter(glTarget(m_t), gl_const::GLBufferSize);
ASSERT_EQUAL(size, byteCapacity, ());
ASSERT_LESS_OR_EQUAL(byteOffset + byteCount, size, ());
#endif
if (IsMapBufferSupported())
{
ASSERT(gpuPtr != nullptr, ());
memcpy((uint8_t *)gpuPtr + byteOffset, data, byteCount);
}
else
{
ASSERT(gpuPtr == nullptr, ());
if (byteOffset == 0 && byteCount == byteCapacity)
GLFunctions::glBufferData(glTarget(m_t), byteCount, data, gl_const::GLDynamicDraw);
else
GLFunctions::glBufferSubData(glTarget(m_t), byteCount, data, byteOffset);
}
}
void GPUBuffer::Unmap()
{
#ifdef DEBUG
ASSERT(m_isMapped, ());
m_isMapped = false;
#endif
m_mappingOffset = 0;
if (IsMapBufferSupported())
GLFunctions::glUnmapBuffer(glTarget(m_t));
}
void GPUBuffer::Resize(void const * data, uint32_t elementCount)
{
TBase::Resize(elementCount);
Bind();
GLFunctions::glBufferData(glTarget(m_t), GetCapacity() * GetElementSize(), data, gl_const::GLDynamicDraw);
// If we have set up data already (in glBufferData), we have to call SetDataSize.
if (data != nullptr)
SetDataSize(elementCount);
#if defined(TRACK_GPU_MEM)
dp::GPUMemTracker & memTracker = dp::GPUMemTracker::Inst();
memTracker.RemoveDeallocated("VBO", m_bufferID);
auto const sizeInBytes = GetCapacity() * GetElementSize();
memTracker.AddAllocated("VBO", m_bufferID, sizeInBytes);
memTracker.TrackAverageAllocation(m_batcherHash, sizeInBytes);
if (data != nullptr)
dp::GPUMemTracker::Inst().SetUsed("VBO", m_bufferID, GetCurrentSize() * GetElementSize());
#endif
}
} // namespace dp

46
libs/drape/gpu_buffer.hpp Normal file
View file

@ -0,0 +1,46 @@
#pragma once
#include "drape/buffer_base.hpp"
#include "drape/drape_diagnostics.hpp"
#include "drape/pointers.hpp"
namespace dp
{
class GPUBuffer : public BufferBase
{
using TBase = BufferBase;
public:
enum Target
{
ElementBuffer,
IndexBuffer
};
GPUBuffer(Target t, void const * data, uint8_t elementSize, uint32_t capacity, uint64_t batcherHash);
~GPUBuffer() override;
void UploadData(void const * data, uint32_t elementCount);
void Bind();
void * Map(uint32_t elementOffset, uint32_t elementCount);
void UpdateData(void * gpuPtr, void const * data, uint32_t elementOffset, uint32_t elementCount);
void Unmap();
protected:
// Discard old data.
void Resize(void const * data, uint32_t elementCount);
private:
Target m_t;
uint32_t m_bufferID;
uint32_t m_mappingOffset;
#ifdef TRACK_GPU_MEM
uint64_t m_batcherHash;
#endif
#ifdef DEBUG
bool m_isMapped;
#endif
};
} // namespace dp

View file

@ -0,0 +1,22 @@
#pragma once
#include <string>
namespace dp
{
class GpuProgram
{
public:
explicit GpuProgram(std::string const & programName) : m_programName(programName) {}
virtual ~GpuProgram() = default;
std::string const & GetName() const { return m_programName; }
virtual void Bind() = 0;
virtual void Unbind() = 0;
protected:
std::string const m_programName;
};
} // namespace dp

View file

@ -0,0 +1,94 @@
#pragma once
#include "drape/drape_global.hpp"
#include "drape/pointers.hpp"
#include <string>
namespace dp
{
enum ClearBits : uint32_t
{
ColorBit = 1,
DepthBit = 1 << 1,
StencilBit = 1 << 2
};
uint32_t constexpr kClearBitsStoreAll = ClearBits::ColorBit | ClearBits::DepthBit | ClearBits::StencilBit;
enum class TestFunction : uint8_t
{
Never = 0,
Less,
Equal,
LessOrEqual,
Greater,
NotEqual,
GreaterOrEqual,
Always
};
enum class StencilFace : uint8_t
{
Front = 0,
Back,
FrontAndBack
};
enum class StencilAction : uint8_t
{
Keep = 0,
Zero,
Replace,
Increment,
IncrementWrap,
Decrement,
DecrementWrap,
Invert
};
class GraphicsContext
{
public:
virtual ~GraphicsContext() = default;
virtual bool BeginRendering() { return true; }
virtual void EndRendering() {}
virtual void Present() = 0;
virtual void MakeCurrent() = 0;
virtual void DoneCurrent() {}
// The value 'nullptr' means default(system) framebuffer.
virtual void SetFramebuffer(ref_ptr<BaseFramebuffer> framebuffer) = 0;
virtual void ForgetFramebuffer(ref_ptr<BaseFramebuffer> framebuffer) = 0;
virtual void ApplyFramebuffer(std::string const & framebufferLabel) = 0;
// w, h - pixel size of render target (logical size * visual scale).
virtual void Resize(uint32_t /* w */, uint32_t /* h */) {}
virtual void SetRenderingEnabled(bool /* enabled */) {}
virtual void SetPresentAvailable(bool /* available */) {}
virtual bool Validate() { return true; }
virtual void CollectMemory() {}
virtual void Init(ApiVersion apiVersion) = 0;
virtual ApiVersion GetApiVersion() const = 0;
virtual std::string GetRendererName() const = 0;
virtual std::string GetRendererVersion() const = 0;
virtual bool HasPartialTextureUpdates() const { return true; }
virtual void DebugSynchronizeWithCPU() {}
virtual void PushDebugLabel(std::string const & label) = 0;
virtual void PopDebugLabel() = 0;
virtual void SetClearColor(Color const & color) = 0;
virtual void Clear(uint32_t clearBits, uint32_t storeBits) = 0;
virtual void Flush() = 0;
virtual void SetViewport(uint32_t x, uint32_t y, uint32_t w, uint32_t h) = 0;
virtual void SetScissor(uint32_t x, uint32_t y, uint32_t w, uint32_t h) = 0;
virtual void SetDepthTestEnabled(bool enabled) = 0;
virtual void SetDepthTestFunction(TestFunction depthFunction) = 0;
virtual void SetStencilTestEnabled(bool enabled) = 0;
virtual void SetStencilFunction(StencilFace face, TestFunction stencilFunction) = 0;
virtual void SetStencilActions(StencilFace face, StencilAction stencilFailAction, StencilAction depthFailAction,
StencilAction passAction) = 0;
virtual void SetStencilReferenceValue(uint32_t stencilReferenceValue) = 0;
virtual void SetCullingEnabled(bool enabled) = 0;
};
} // namespace dp

View file

@ -0,0 +1,51 @@
#include "drape/graphics_context_factory.hpp"
namespace dp
{
ThreadSafeFactory::ThreadSafeFactory(GraphicsContextFactory * factory, bool enableSharing)
: m_factory(factory)
, m_enableSharing(enableSharing)
{}
ThreadSafeFactory::~ThreadSafeFactory()
{
delete m_factory;
}
GraphicsContext * ThreadSafeFactory::GetDrawContext()
{
return CreateContext([this]() { return m_factory->GetDrawContext(); },
[this]() { return m_factory->IsUploadContextCreated(); });
}
GraphicsContext * ThreadSafeFactory::GetResourcesUploadContext()
{
return CreateContext([this]() { return m_factory->GetResourcesUploadContext(); },
[this]() { return m_factory->IsDrawContextCreated(); });
}
GraphicsContext * ThreadSafeFactory::CreateContext(TCreateCtxFn const & createFn, TIsSeparateCreatedFn const & checkFn)
{
std::unique_lock<std::mutex> lock(m_condLock);
GraphicsContext * ctx = createFn();
if (m_enableSharing)
{
// Wait until context is created.
m_Cond.wait(lock, checkFn);
m_Cond.notify_one();
}
return ctx;
}
void ThreadSafeFactory::WaitForInitialization(GraphicsContext * context)
{
m_factory->WaitForInitialization(context);
}
void ThreadSafeFactory::SetPresentAvailable(bool available)
{
m_factory->SetPresentAvailable(available);
}
} // namespace dp

View file

@ -0,0 +1,53 @@
#pragma once
#include "drape/graphics_context.hpp"
#include "base/assert.hpp"
#include <condition_variable>
#include <functional>
#include <mutex>
namespace dp
{
class GraphicsContextFactory
{
public:
virtual ~GraphicsContextFactory() = default;
virtual GraphicsContext * GetDrawContext() = 0;
virtual GraphicsContext * GetResourcesUploadContext() = 0;
virtual bool IsDrawContextCreated() const { return false; }
virtual bool IsUploadContextCreated() const { return false; }
virtual void WaitForInitialization(dp::GraphicsContext * context) {}
virtual void SetPresentAvailable(bool available) {}
};
class ThreadSafeFactory : public GraphicsContextFactory
{
public:
ThreadSafeFactory(GraphicsContextFactory * factory, bool enableSharing = true);
~ThreadSafeFactory();
GraphicsContext * GetDrawContext() override;
GraphicsContext * GetResourcesUploadContext() override;
template <typename T>
T * CastFactory()
{
return dynamic_cast<T *>(m_factory);
}
void WaitForInitialization(dp::GraphicsContext * context) override;
void SetPresentAvailable(bool available) override;
protected:
using TCreateCtxFn = std::function<GraphicsContext *()>;
using TIsSeparateCreatedFn = std::function<bool()>;
GraphicsContext * CreateContext(TCreateCtxFn const & createFn, TIsSeparateCreatedFn const & checkFn);
private:
GraphicsContextFactory * m_factory;
std::mutex m_condLock;
std::condition_variable m_Cond;
bool m_enableSharing;
};
} // namespace dp

View file

@ -0,0 +1,234 @@
#include "drape/harfbuzz_shaping.hpp"
#include "base/assert.hpp"
#include "base/logging.hpp"
#include "base/string_utils.hpp"
#include <array>
#include <sstream>
#include <string>
#include <unicode/ubidi.h> // ubidi_open, ubidi_setPara
#include <unicode/uscript.h> // UScriptCode
#include <utf8/unchecked.h>
namespace harfbuzz_shaping
{
namespace
{
// Some Unicode characters may be a part of up to 32 different scripts.
using TScriptsArray = std::array<UScriptCode, 32>;
// Writes the script and the script extensions of the Unicode codepoint.
// Returns the number of written scripts.
size_t GetScriptExtensions(char32_t codepoint, TScriptsArray & scripts)
{
// Fill scripts with the script extensions.
UErrorCode icu_error = U_ZERO_ERROR;
size_t const count = uscript_getScriptExtensions(static_cast<UChar32>(codepoint), scripts.data(),
static_cast<int32_t>(scripts.max_size()), &icu_error);
if (U_FAILURE(icu_error))
{
LOG(LWARNING, ("uscript_getScriptExtensions failed with error", icu_error));
return 0;
}
return count;
}
// Intersects the script extensions set of codepoint with scripts and returns the updated size of the scripts.
// The output result will be a subset of the input result (thus resultSize can only be smaller).
size_t ScriptSetIntersect(char32_t codepoint, TScriptsArray & inOutScripts, size_t inOutScriptsCount)
{
// Each codepoint has a Script property and a Script Extensions (Scx) property.
//
// The implicit Script property values 'Common' and 'Inherited' indicate that a codepoint is widely used in many
// scripts, rather than being associated to a specific script.
//
// However, some codepoints that are assigned a value of 'Common' or 'Inherited' are not commonly used with all
// scripts, but rather only with a limited set of scripts. The Script Extension property is used to specify the set
// of script which borrow the codepoint.
//
// Calls to GetScriptExtensions(...) return the set of scripts where the codepoints can be used.
// (see table 7 from http://www.unicode.org/reports/tr24/tr24-29.html)
//
// Script Script Extensions -> Results
// 1) Common {Common} -> {Common}
// Inherited {Inherited} -> {Inherited}
// 2) Latin {Latn} -> {Latn}
// Inherited {Latn} -> {Latn}
// 3) Common {Hira Kana} -> {Hira Kana}
// Inherited {Hira Kana} -> {Hira Kana}
// 4) Devanagari {Deva Dogr Kthi Mahj} -> {Deva Dogr Kthi Mahj}
// Myanmar {Cakm Mymr Tale} -> {Cakm Mymr Tale}
//
// For most of the codepoints, the script extensions set contains only one element. For CJK codepoints, it's common
// to see 3-4 scripts. For really rare cases, the set can go above 20 scripts.
TScriptsArray codepointScripts;
size_t const codepointScriptsCount = GetScriptExtensions(codepoint, codepointScripts);
// Implicit script 'inherited' is inheriting scripts from preceding codepoint.
if (codepointScriptsCount == 1 && codepointScripts[0] == USCRIPT_INHERITED)
return inOutScriptsCount;
auto const contains = [&codepointScripts, codepointScriptsCount](UScriptCode code)
{
for (size_t i = 0; i < codepointScriptsCount; ++i)
if (codepointScripts[i] == code)
return true;
return false;
};
// Intersect both script sets.
ASSERT(!contains(USCRIPT_INHERITED), ());
size_t outSize = 0;
for (size_t i = 0; i < inOutScriptsCount; ++i)
{
auto const currentScript = inOutScripts[i];
if (contains(currentScript))
inOutScripts[outSize++] = currentScript;
}
return outSize;
}
// Find the longest sequence of characters from 0 and up to length that have at least one common UScriptCode value.
// Writes the common script value to script and returns the length of the sequence. Takes the characters' script
// extensions into account. http://www.unicode.org/reports/tr24/#ScriptX
//
// Consider 3 characters with the script values {Kana}, {Hira, Kana}, {Kana}. Without script extensions only the first
// script in each set would be taken into account, resulting in 3 segments where 1 would be enough.
size_t ScriptInterval(std::u16string const & text, int32_t start, size_t length, UScriptCode & outScript)
{
ASSERT_GREATER(length, 0U, ());
auto const begin = text.begin() + start;
auto const end = text.begin() + start + static_cast<int32_t>(length);
auto iterator = begin;
auto c32 = utf8::unchecked::next16(iterator);
TScriptsArray scripts;
size_t scriptsSize = GetScriptExtensions(c32, scripts);
while (iterator != end)
{
c32 = utf8::unchecked::next16(iterator);
scriptsSize = ScriptSetIntersect(c32, scripts, scriptsSize);
if (scriptsSize == 0U)
{
length = iterator - begin - 1;
break;
}
}
outScript = scripts[0];
return length;
}
// A copy of hb_icu_script_to_script to avoid direct ICU dependency.
hb_script_t ICUScriptToHarfbuzzScript(UScriptCode script)
{
if (script == USCRIPT_INVALID_CODE)
return HB_SCRIPT_INVALID;
return hb_script_from_string(uscript_getShortName(script), -1);
}
void GetSingleTextLineRuns(TextSegments & segments)
{
auto const & text = segments.m_text;
auto const textLength = static_cast<int32_t>(text.length());
// Deliberately not checking for nullptr.
thread_local UBiDi * const bidi = ubidi_open();
UErrorCode error = U_ZERO_ERROR;
::ubidi_setPara(bidi, text.data(), textLength, UBIDI_DEFAULT_LTR, nullptr, &error);
if (U_FAILURE(error))
{
LOG(LERROR, ("ubidi_setPara failed with code", error));
segments.m_segments.emplace_back(0, 0, HB_SCRIPT_UNKNOWN, HB_DIRECTION_INVALID);
return;
}
// Split the original text by logical runs, then each logical run by common script and each sequence at special
// characters and style boundaries. This invariant holds: bidiRunStart <= scriptRunStart <= breakingRunStart
// <= breakingRunEnd <= scriptRunStart <= bidiRunEnd. AB: Breaking runs are dropped now, they may not be needed.
for (int32_t bidiRunStart = 0; bidiRunStart < textLength;)
{
// Determine the longest logical run (e.g. same bidi direction) from this point.
int32_t bidiRunBreak = 0;
UBiDiLevel bidiLevel = 0;
::ubidi_getLogicalRun(bidi, bidiRunStart, &bidiRunBreak, &bidiLevel);
int32_t const bidiRunEnd = bidiRunBreak;
ASSERT_LESS(bidiRunStart, bidiRunEnd, ());
for (int32_t scriptRunStart = bidiRunStart; scriptRunStart < bidiRunEnd;)
{
// Find the longest sequence of characters that have at least one common UScriptCode value.
UScriptCode script = USCRIPT_INVALID_CODE;
size_t const scriptRunEnd =
ScriptInterval(segments.m_text, scriptRunStart, bidiRunEnd - scriptRunStart, script) + scriptRunStart;
ASSERT_LESS(scriptRunStart, base::asserted_cast<int32_t>(scriptRunEnd), ());
// TODO(AB): May need to break on different unicode blocks, parentheses, and control chars (spaces).
// TODO(AB): Support vertical layouts if necessary.
segments.m_segments.emplace_back(scriptRunStart, scriptRunEnd - scriptRunStart, ICUScriptToHarfbuzzScript(script),
bidiLevel & 0x01 ? HB_DIRECTION_RTL : HB_DIRECTION_LTR);
// Move to the next script sequence.
scriptRunStart = static_cast<int32_t>(scriptRunEnd);
}
// Move to the next direction sequence.
bidiRunStart = bidiRunEnd;
}
}
void ReorderRTL(TextSegments & segments)
{
// TODO(AB): Optimize implementation to use indexes to segments instead of copying them.
auto it = segments.m_segments.begin();
auto const end = segments.m_segments.end();
// TODO(AB): Line (default rendering) direction is determined by the first segment. It should be defined as
// a parameter depending on the language.
auto const lineDirection = it->m_direction;
while (it != end)
{
if (it->m_direction == lineDirection)
++it;
else
{
auto const start = it++;
while (it != end && it->m_direction != lineDirection)
++it;
std::reverse(start, it);
}
}
if (lineDirection != HB_DIRECTION_LTR)
std::reverse(segments.m_segments.begin(), end);
}
} // namespace
TextSegments GetTextSegments(std::string_view utf8)
{
ASSERT(!utf8.empty(), ("Shaping of empty strings is not supported"));
ASSERT(std::string::npos == utf8.find_first_of("\n"), ("Shaping with line breaks is not supported", utf8));
// TODO(AB): Can unnecessary conversion/allocation be avoided?
TextSegments segments{strings::ToUtf16(utf8), {}};
// TODO(AB): Runs are not split by breaking chars and by different fonts.
GetSingleTextLineRuns(segments);
ReorderRTL(segments);
return segments;
}
std::string DebugPrint(TextSegment const & segment)
{
std::stringstream ss;
ss << "TextSegment[start=" << segment.m_start << ", length=" << segment.m_length << ", script=" << segment.m_script
<< ", direction=" << segment.m_direction << ']';
return ss.str();
}
} // namespace harfbuzz_shaping

View file

@ -0,0 +1,39 @@
#pragma once
#include <string>
#include <vector>
#include <hb.h>
namespace harfbuzz_shaping
{
struct TextSegment
{
// TODO(AB): Use 1 byte or 2 bytes for memory-efficient caching.
int32_t m_start; // Offset to the segment start in the string.
int32_t m_length;
hb_script_t m_script;
hb_direction_t m_direction;
TextSegment(int32_t start, int32_t length, hb_script_t script, hb_direction_t direction)
: m_start(start)
, m_length(length)
, m_script(script)
, m_direction(direction)
{}
};
struct TextSegments
{
std::u16string m_text;
std::vector<TextSegment> m_segments;
// TODO(AB): Reverse indexes to order segments instead of moving them.
// std::vector<size_t> m_segmentsOrder;
};
// Finds segments with common properties suitable for harfbuzz in a single line of text without newlines.
// Any line breaking/trimming should be done by the caller.
TextSegments GetTextSegments(std::string_view utf8);
std::string DebugPrint(TextSegment const & segment);
} // namespace harfbuzz_shaping

303
libs/drape/hw_texture.cpp Normal file
View file

@ -0,0 +1,303 @@
#include "drape/hw_texture.hpp"
#include "drape/drape_global.hpp"
#include "drape/gl_functions.hpp"
#include "drape/utils/gpu_mem_tracker.hpp"
#include "base/logging.hpp"
#include "std/target_os.hpp"
#if defined(OMIM_OS_IPHONE)
#include "drape/hw_texture_ios.hpp"
#endif
#if defined(OMIM_METAL_AVAILABLE)
extern drape_ptr<dp::HWTextureAllocator> CreateMetalAllocator();
extern ref_ptr<dp::HWTextureAllocator> GetDefaultMetalAllocator();
#endif
extern drape_ptr<dp::HWTextureAllocator> CreateVulkanAllocator();
extern ref_ptr<dp::HWTextureAllocator> GetDefaultVulkanAllocator();
namespace dp
{
std::string DebugPrint(HWTexture::Params const & p)
{
std::ostringstream ss;
ss << "Width = " << p.m_width << "; Height = " << p.m_height << "; Format = " << DebugPrint(p.m_format)
<< "; IsRenderTarget = " << p.m_isRenderTarget;
return ss.str();
}
void UnpackFormat(ref_ptr<dp::GraphicsContext> context, TextureFormat format, glConst & layout, glConst & pixelType)
{
auto const apiVersion = context->GetApiVersion();
CHECK(apiVersion == dp::ApiVersion::OpenGLES3, ());
switch (format)
{
case TextureFormat::RGBA8:
layout = gl_const::GLRGBA;
pixelType = gl_const::GL8BitOnChannel;
return;
case TextureFormat::Red:
layout = gl_const::GLRed;
pixelType = gl_const::GL8BitOnChannel;
return;
case TextureFormat::RedGreen:
layout = gl_const::GLRedGreen;
pixelType = gl_const::GL8BitOnChannel;
return;
case TextureFormat::DepthStencil:
layout = gl_const::GLDepthStencil;
pixelType = gl_const::GLUnsignedInt24_8Type;
return;
case TextureFormat::Depth:
layout = gl_const::GLDepthComponent;
pixelType = gl_const::GLUnsignedIntType;
return;
case TextureFormat::Unspecified: CHECK(false, ()); return;
}
UNREACHABLE();
}
glConst DecodeTextureFilter(TextureFilter filter)
{
switch (filter)
{
case TextureFilter::Linear: return gl_const::GLLinear;
case TextureFilter::Nearest: return gl_const::GLNearest;
}
UNREACHABLE();
}
glConst DecodeTextureWrapping(TextureWrapping wrapping)
{
switch (wrapping)
{
case TextureWrapping::ClampToEdge: return gl_const::GLClampToEdge;
case TextureWrapping::Repeat: return gl_const::GLRepeat;
}
UNREACHABLE();
}
HWTexture::~HWTexture()
{
#if defined(TRACK_GPU_MEM)
dp::GPUMemTracker::Inst().RemoveDeallocated("Texture", m_textureID);
dp::GPUMemTracker::Inst().RemoveDeallocated("PBO", m_pixelBufferID);
#endif
}
void HWTexture::Create(ref_ptr<dp::GraphicsContext> context, Params const & params)
{
Create(context, params, nullptr);
}
void HWTexture::Create(ref_ptr<dp::GraphicsContext>, Params const & params, ref_ptr<void>)
{
m_params = params;
#if defined(TRACK_GPU_MEM)
uint32_t const memSize = (CHAR_BIT * bytesPerPixel * m_params.m_width * m_params.m_height) >> 3;
dp::GPUMemTracker::Inst().AddAllocated("Texture", m_textureID, memSize);
dp::GPUMemTracker::Inst().SetUsed("Texture", m_textureID, memSize);
if (params.m_usePixelBuffer)
{
dp::GPUMemTracker::Inst().AddAllocated("PBO", m_pixelBufferID, m_pixelBufferSize);
dp::GPUMemTracker::Inst().SetUsed("PBO", m_pixelBufferID, m_pixelBufferSize);
}
#endif
}
TextureFormat HWTexture::GetFormat() const
{
return m_params.m_format;
}
uint32_t HWTexture::GetWidth() const
{
ASSERT(Validate(), ());
return m_params.m_width;
}
uint32_t HWTexture::GetHeight() const
{
ASSERT(Validate(), ());
return m_params.m_height;
}
float HWTexture::GetS(uint32_t x) const
{
ASSERT(Validate(), ());
return x / static_cast<float>(m_params.m_width);
}
float HWTexture::GetT(uint32_t y) const
{
ASSERT(Validate(), ());
return y / static_cast<float>(m_params.m_height);
}
uint32_t HWTexture::GetID() const
{
return m_textureID;
}
OpenGLHWTexture::~OpenGLHWTexture()
{
if (m_textureID != 0)
GLFunctions::glDeleteTexture(m_textureID);
if (m_pixelBufferID != 0)
GLFunctions::glDeleteBuffer(m_pixelBufferID);
}
void OpenGLHWTexture::Create(ref_ptr<dp::GraphicsContext> context, Params const & params, ref_ptr<void> data)
{
Base::Create(context, params, data);
CHECK(context && m_textureID == 0 && m_pixelBufferID == 0, ());
// OpenGL ES 2.0 does not support PBO.
if (params.m_usePixelBuffer && context->GetApiVersion() == dp::ApiVersion::OpenGLES3)
{
m_pixelBufferElementSize = GetBytesPerPixel(m_params.m_format);
if (m_pixelBufferElementSize > 0)
{
// Buffer size = 10%
m_pixelBufferSize = static_cast<uint32_t>(0.1 * m_params.m_width * m_params.m_height * m_pixelBufferElementSize);
}
}
m_textureID = GLFunctions::glGenTexture();
Bind(context);
UnpackFormat(context, m_params.m_format, m_unpackedLayout, m_unpackedPixelType);
auto const f = DecodeTextureFilter(m_params.m_filter);
GLFunctions::glTexImage2D(m_params.m_width, m_params.m_height, m_unpackedLayout, m_unpackedPixelType, data.get());
GLFunctions::glTexParameter(gl_const::GLMinFilter, f);
GLFunctions::glTexParameter(gl_const::GLMagFilter, f);
GLFunctions::glTexParameter(gl_const::GLWrapS, DecodeTextureWrapping(m_params.m_wrapSMode));
GLFunctions::glTexParameter(gl_const::GLWrapT, DecodeTextureWrapping(m_params.m_wrapTMode));
if (m_pixelBufferSize > 0)
{
m_pixelBufferID = GLFunctions::glGenBuffer();
GLFunctions::glBindBuffer(m_pixelBufferID, gl_const::GLPixelBufferWrite);
GLFunctions::glBufferData(gl_const::GLPixelBufferWrite, m_pixelBufferSize, nullptr, gl_const::GLDynamicDraw);
GLFunctions::glBindBuffer(0, gl_const::GLPixelBufferWrite);
}
GLFunctions::glBindTexture(0);
GLFunctions::glFlush();
}
void OpenGLHWTexture::UploadData(ref_ptr<dp::GraphicsContext> context, uint32_t x, uint32_t y, uint32_t width,
uint32_t height, ref_ptr<void> data)
{
ASSERT(Validate(), ());
uint32_t const mappingSize = height * width * m_pixelBufferElementSize;
if (m_pixelBufferID != 0 && m_pixelBufferSize >= mappingSize)
{
ASSERT_GREATER(mappingSize, 0, ());
GLFunctions::glBindBuffer(m_pixelBufferID, gl_const::GLPixelBufferWrite);
GLFunctions::glBufferSubData(gl_const::GLPixelBufferWrite, mappingSize, data.get(), 0);
GLFunctions::glTexSubImage2D(x, y, width, height, m_unpackedLayout, m_unpackedPixelType, nullptr);
GLFunctions::glBindBuffer(0, gl_const::GLPixelBufferWrite);
}
else
{
GLFunctions::glTexSubImage2D(x, y, width, height, m_unpackedLayout, m_unpackedPixelType, data.get());
}
}
void OpenGLHWTexture::Bind(ref_ptr<dp::GraphicsContext> context) const
{
UNUSED_VALUE(context);
ASSERT(Validate(), ());
if (m_textureID != 0)
GLFunctions::glBindTexture(GetID());
}
void OpenGLHWTexture::SetFilter(TextureFilter filter)
{
ASSERT(Validate(), ());
if (m_params.m_filter != filter)
{
m_params.m_filter = filter;
auto const f = DecodeTextureFilter(m_params.m_filter);
GLFunctions::glTexParameter(gl_const::GLMinFilter, f);
GLFunctions::glTexParameter(gl_const::GLMagFilter, f);
}
}
bool OpenGLHWTexture::Validate() const
{
return GetID() != 0;
}
drape_ptr<HWTexture> OpenGLHWTextureAllocator::CreateTexture(ref_ptr<dp::GraphicsContext> context)
{
UNUSED_VALUE(context);
return make_unique_dp<OpenGLHWTexture>();
}
void OpenGLHWTextureAllocator::Flush()
{
GLFunctions::glFlush();
}
drape_ptr<HWTextureAllocator> CreateAllocator(ref_ptr<dp::GraphicsContext> context)
{
CHECK(context != nullptr, ());
auto const apiVersion = context->GetApiVersion();
if (apiVersion == dp::ApiVersion::Metal)
{
#if defined(OMIM_METAL_AVAILABLE)
return CreateMetalAllocator();
#endif
CHECK(false, ("Metal rendering is supported now only on iOS."));
return nullptr;
}
if (apiVersion == dp::ApiVersion::Vulkan)
return CreateVulkanAllocator();
if (apiVersion == dp::ApiVersion::OpenGLES3)
return make_unique_dp<OpenGLHWTextureAllocator>();
#if defined(OMIM_OS_IPHONE)
return make_unique_dp<HWTextureAllocatorApple>();
#else
return make_unique_dp<OpenGLHWTextureAllocator>();
#endif
}
ref_ptr<HWTextureAllocator> GetDefaultAllocator(ref_ptr<dp::GraphicsContext> context)
{
CHECK(context != nullptr, ());
auto const apiVersion = context->GetApiVersion();
if (apiVersion == dp::ApiVersion::Metal)
{
#if defined(OMIM_METAL_AVAILABLE)
return GetDefaultMetalAllocator();
#endif
CHECK(false, ("Metal rendering is supported now only on iOS."));
return nullptr;
}
if (apiVersion == dp::ApiVersion::Vulkan)
return GetDefaultVulkanAllocator();
static OpenGLHWTextureAllocator s_allocator;
return make_ref<HWTextureAllocator>(&s_allocator);
}
} // namespace dp

108
libs/drape/hw_texture.hpp Normal file
View file

@ -0,0 +1,108 @@
#pragma once
#include "drape/gl_constants.hpp"
#include "drape/graphics_context.hpp"
#include "drape/pointers.hpp"
#include "drape/texture_types.hpp"
#include <cstdint>
namespace dp
{
class HWTextureAllocator;
class HWTexture
{
public:
virtual ~HWTexture();
struct Params
{
uint32_t m_width = 0;
uint32_t m_height = 0;
TextureFilter m_filter = TextureFilter::Linear;
TextureWrapping m_wrapSMode = TextureWrapping::ClampToEdge;
TextureWrapping m_wrapTMode = TextureWrapping::ClampToEdge;
TextureFormat m_format = TextureFormat::Unspecified;
bool m_usePixelBuffer = false;
bool m_isRenderTarget = false;
bool m_isMutable = false;
ref_ptr<HWTextureAllocator> m_allocator;
friend std::string DebugPrint(Params const & p);
};
void Create(ref_ptr<dp::GraphicsContext> context, Params const & params);
virtual void Create(ref_ptr<dp::GraphicsContext> context, Params const & params, ref_ptr<void> data) = 0;
virtual void UploadData(ref_ptr<dp::GraphicsContext> context, uint32_t x, uint32_t y, uint32_t width, uint32_t height,
ref_ptr<void> data) = 0;
virtual void Bind(ref_ptr<dp::GraphicsContext> context) const = 0;
// For OpenGL the texture must be bound before calling this method.
virtual void SetFilter(TextureFilter filter) = 0;
virtual bool Validate() const = 0;
TextureFormat GetFormat() const;
uint32_t GetWidth() const;
uint32_t GetHeight() const;
float GetS(uint32_t x) const;
float GetT(uint32_t y) const;
Params const & GetParams() const { return m_params; }
uint32_t GetID() const;
protected:
Params m_params;
uint32_t m_textureID = 0;
};
class HWTextureAllocator
{
public:
virtual ~HWTextureAllocator() = default;
virtual drape_ptr<HWTexture> CreateTexture(ref_ptr<dp::GraphicsContext> context) = 0;
virtual void Flush() = 0;
};
class OpenGLHWTexture : public HWTexture
{
using Base = HWTexture;
public:
~OpenGLHWTexture() override;
void Create(ref_ptr<dp::GraphicsContext> context, Params const & params, ref_ptr<void> data) override;
void UploadData(ref_ptr<dp::GraphicsContext> context, uint32_t x, uint32_t y, uint32_t width, uint32_t height,
ref_ptr<void> data) override;
void Bind(ref_ptr<dp::GraphicsContext> context) const override;
void SetFilter(TextureFilter filter) override;
bool Validate() const override;
private:
uint32_t m_pixelBufferID = 0;
uint32_t m_pixelBufferSize = 0;
uint8_t m_pixelBufferElementSize = 0;
glConst m_unpackedLayout = 0;
glConst m_unpackedPixelType = 0;
};
class OpenGLHWTextureAllocator : public HWTextureAllocator
{
public:
drape_ptr<HWTexture> CreateTexture(ref_ptr<dp::GraphicsContext> context) override;
void Flush() override;
};
ref_ptr<HWTextureAllocator> GetDefaultAllocator(ref_ptr<dp::GraphicsContext> context);
drape_ptr<HWTextureAllocator> CreateAllocator(ref_ptr<dp::GraphicsContext> context);
void UnpackFormat(ref_ptr<dp::GraphicsContext> context, TextureFormat format, glConst & layout, glConst & pixelType);
glConst DecodeTextureFilter(TextureFilter filter);
glConst DecodeTextureWrapping(TextureWrapping wrapping);
} // namespace dp

View file

@ -0,0 +1,66 @@
#pragma once
#include "drape/gl_constants.hpp"
#include "drape/hw_texture.hpp"
#include "std/target_os.hpp"
#ifndef OMIM_OS_IPHONE
#error Only for iOS
#endif
#import <CoreVideo/CVOpenGLESTexture.h>
#import <CoreVideo/CVOpenGLESTextureCache.h>
#import <CoreVideo/CVPixelBuffer.h>
namespace dp
{
class HWTextureAllocatorApple : public HWTextureAllocator
{
public:
HWTextureAllocatorApple();
~HWTextureAllocatorApple();
CVPixelBufferRef CVCreatePixelBuffer(uint32_t width, uint32_t height, dp::TextureFormat format);
void CVDestroyPixelBuffer(CVPixelBufferRef buffer);
CVOpenGLESTextureRef CVCreateTexture(CVPixelBufferRef buffer, uint32_t width, uint32_t height, glConst layout,
glConst pixelType);
void CVDestroyTexture(CVOpenGLESTextureRef texture);
void RiseFlushFlag();
drape_ptr<HWTexture> CreateTexture(ref_ptr<dp::GraphicsContext> context) override;
void Flush() override;
private:
bool m_needFlush;
CVOpenGLESTextureCacheRef m_textureCache;
};
class HWTextureApple : public HWTexture
{
using TBase = HWTexture;
public:
explicit HWTextureApple(ref_ptr<HWTextureAllocatorApple> allocator);
~HWTextureApple();
void Create(ref_ptr<dp::GraphicsContext> context, Params const & params, ref_ptr<void> data) override;
void UploadData(ref_ptr<dp::GraphicsContext> context, uint32_t x, uint32_t y, uint32_t width, uint32_t height,
ref_ptr<void> data) override;
void Bind(ref_ptr<dp::GraphicsContext> context) const override;
void SetFilter(TextureFilter filter) override;
bool Validate() const override;
private:
void Lock();
void Unlock();
CVPixelBufferRef m_directBuffer;
CVOpenGLESTextureRef m_texture;
ref_ptr<HWTextureAllocatorApple> m_allocator;
void * m_directPointer;
};
} // namespace dp

View file

@ -0,0 +1,236 @@
#include "drape/hw_texture_ios.hpp"
#include "drape/gl_functions.hpp"
#include "drape/gl_includes.hpp"
#include "base/logging.hpp"
#import <QuartzCore/CAEAGLLayer.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSValue.h>
#include <boost/integer_traits.hpp>
#include <boost/gil/algorithm.hpp>
#include <boost/gil/typedefs.hpp>
using boost::gil::gray8c_pixel_t;
using boost::gil::gray8_pixel_t;
using boost::gil::gray8c_view_t;
using boost::gil::gray8_view_t;
using boost::gil::rgba8c_pixel_t;
using boost::gil::rgba8_pixel_t;
using boost::gil::rgba8c_view_t;
using boost::gil::rgba8_view_t;
using boost::gil::interleaved_view;
using boost::gil::subimage_view;
using boost::gil::copy_pixels;
namespace dp
{
HWTextureAllocatorApple::HWTextureAllocatorApple()
{
CVReturn cvRetval = CVOpenGLESTextureCacheCreate(kCFAllocatorDefault, nullptr,
[EAGLContext currentContext],
nullptr, &m_textureCache);
CHECK_EQUAL(cvRetval, kCVReturnSuccess, ());
}
HWTextureAllocatorApple::~HWTextureAllocatorApple()
{
CFRelease(m_textureCache);
}
CVPixelBufferRef HWTextureAllocatorApple::CVCreatePixelBuffer(uint32_t width, uint32_t height,
dp::TextureFormat format)
{
NSDictionary * attrs = [NSDictionary dictionaryWithObjectsAndKeys:
[NSDictionary dictionary], kCVPixelBufferIOSurfacePropertiesKey,
[NSNumber numberWithInt:16], kCVPixelBufferBytesPerRowAlignmentKey,
[NSNumber numberWithBool:YES], kCVPixelBufferOpenGLESCompatibilityKey,
nil];
CFDictionaryRef attrsRef = (__bridge CFDictionaryRef)attrs;
CVPixelBufferRef result = nullptr;
CVReturn cvRetval = 0;
switch (format)
{
case dp::TextureFormat::RGBA8:
cvRetval = CVPixelBufferCreate(kCFAllocatorDefault, width, height, kCVPixelFormatType_32BGRA,
attrsRef, &result);
break;
case dp::TextureFormat::Red:
cvRetval = CVPixelBufferCreate(kCFAllocatorDefault, width, height, kCVPixelFormatType_OneComponent8,
attrsRef, &result);
break;
default:
ASSERT(false, ());
break;
}
CHECK_EQUAL(cvRetval, kCVReturnSuccess, ());
return result;
}
void HWTextureAllocatorApple::CVDestroyPixelBuffer(CVPixelBufferRef buffer)
{
CFRelease(buffer);
}
CVOpenGLESTextureRef HWTextureAllocatorApple::CVCreateTexture(CVPixelBufferRef buffer, uint32_t width, uint32_t height,
glConst layout, glConst pixelType)
{
CVOpenGLESTextureRef texture;
CVReturn cvRetval = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault, m_textureCache, buffer,
nullptr, gl_const::GLTexture2D, layout,
width, height, layout, pixelType, 0, &texture);
CHECK_EQUAL(cvRetval, kCVReturnSuccess, ());
return texture;
}
void HWTextureAllocatorApple::CVDestroyTexture(CVOpenGLESTextureRef texture)
{
CFRelease(texture);
}
void HWTextureAllocatorApple::RiseFlushFlag()
{
m_needFlush = true;
}
drape_ptr<HWTexture> HWTextureAllocatorApple::CreateTexture(ref_ptr<dp::GraphicsContext> context)
{
UNUSED_VALUE(context);
return make_unique_dp<HWTextureApple>(make_ref(this));
}
void HWTextureAllocatorApple::Flush()
{
if (m_needFlush)
{
CVOpenGLESTextureCacheFlush(m_textureCache, 0);
m_needFlush = false;
}
}
HWTextureApple::HWTextureApple(ref_ptr<HWTextureAllocatorApple> allocator)
: m_directBuffer(nullptr)
, m_texture(nullptr)
, m_allocator(nullptr)
, m_directPointer(nullptr)
{
}
HWTextureApple::~HWTextureApple()
{
if (m_allocator != nullptr)
{
m_allocator->CVDestroyTexture(m_texture);
m_allocator->CVDestroyPixelBuffer(m_directBuffer);
}
}
void HWTextureApple::Create(ref_ptr<dp::GraphicsContext> context, Params const & params, ref_ptr<void> data)
{
TBase::Create(context, params, data);
m_allocator = m_params.m_allocator;
m_directBuffer = m_allocator->CVCreatePixelBuffer(m_params.m_width, m_params.m_height, params.m_format);
glConst layout, pixelType;
UnpackFormat(context, params.m_format, layout, pixelType);
m_texture = m_allocator->CVCreateTexture(m_directBuffer, params.m_width, params.m_height,
layout, pixelType);
m_textureID = CVOpenGLESTextureGetName(m_texture);
GLFunctions::glBindTexture(m_textureID);
auto const f = DecodeTextureFilter(m_params.m_filter);
GLFunctions::glTexParameter(gl_const::GLMinFilter, f);
GLFunctions::glTexParameter(gl_const::GLMagFilter, f);
GLFunctions::glTexParameter(gl_const::GLWrapS, DecodeTextureWrapping(m_params.m_wrapSMode));
GLFunctions::glTexParameter(gl_const::GLWrapT, DecodeTextureWrapping(m_params.m_wrapTMode));
if (data == nullptr)
return;
Lock();
memcpy(m_directPointer, data.get(), m_params.m_width * m_params.m_height * GetBytesPerPixel(m_params.m_format));
Unlock();
}
void HWTextureApple::UploadData(ref_ptr<dp::GraphicsContext> context, uint32_t x, uint32_t y,
uint32_t width, uint32_t height, ref_ptr<void> data)
{
uint8_t bytesPerPixel = GetBytesPerPixel(GetFormat());
Lock();
if (bytesPerPixel == 1)
{
gray8c_view_t srcView = interleaved_view(width, height, (gray8c_pixel_t *)data.get(), width);
gray8_view_t dstView = interleaved_view(m_params.m_width, m_params.m_height,
(gray8_pixel_t *)m_directPointer, m_params.m_width);
gray8_view_t subDstView = subimage_view(dstView, x, y, width, height);
copy_pixels(srcView, subDstView);
}
else if (bytesPerPixel == 4)
{
rgba8c_view_t srcView = interleaved_view(width, height,
(rgba8c_pixel_t *)data.get(),
width * bytesPerPixel);
rgba8_view_t dstView = interleaved_view(m_params.m_width, m_params.m_height,
(rgba8_pixel_t *)m_directPointer,
m_params.m_width * bytesPerPixel);
rgba8_view_t subDstView = subimage_view(dstView, x, y, width, height);
copy_pixels(srcView, subDstView);
}
Unlock();
}
void HWTextureApple::Bind(ref_ptr<dp::GraphicsContext> context) const
{
UNUSED_VALUE(context);
ASSERT(Validate(), ());
if (m_textureID != 0)
GLFunctions::glBindTexture(GetID());
}
void HWTextureApple::SetFilter(TextureFilter filter)
{
ASSERT(Validate(), ());
if (m_params.m_filter != filter)
{
m_params.m_filter = filter;
auto const f = DecodeTextureFilter(m_params.m_filter);
GLFunctions::glTexParameter(gl_const::GLMinFilter, f);
GLFunctions::glTexParameter(gl_const::GLMagFilter, f);
}
}
bool HWTextureApple::Validate() const
{
return GetID() != 0;
}
void HWTextureApple::Lock()
{
ASSERT(m_directPointer == nullptr, ());
CHECK_EQUAL(CVPixelBufferLockBaseAddress(m_directBuffer, 0), kCVReturnSuccess, ());
ASSERT_EQUAL(CVPixelBufferGetBytesPerRow(m_directBuffer),
m_params.m_width * GetBytesPerPixel(m_params.m_format), ());
m_directPointer = CVPixelBufferGetBaseAddress(m_directBuffer);
}
void HWTextureApple::Unlock()
{
ASSERT(m_directPointer != nullptr, ());
m_directPointer = nullptr;
CHECK_EQUAL(CVPixelBufferUnlockBaseAddress(m_directBuffer, 0), kCVReturnSuccess, ());
m_allocator->RiseFlushFlag();
}
} // namespace dp

View file

@ -0,0 +1,23 @@
#include "drape/index_buffer.hpp"
#include "drape/index_storage.hpp"
#include "base/assert.hpp"
namespace dp
{
IndexBuffer::IndexBuffer(uint32_t capacity) : DataBuffer((uint8_t)IndexStorage::SizeOfIndex(), capacity) {}
void IndexBuffer::UploadData(ref_ptr<GraphicsContext> context, void const * data, uint32_t size)
{
GetBuffer()->UploadData(context, data, size);
}
void IndexBuffer::UpdateData(ref_ptr<GraphicsContext> context, void const * data, uint32_t size)
{
ASSERT_LESS_OR_EQUAL(size, GetBuffer()->GetCapacity(), ());
GetBuffer()->Seek(0);
if (size > 0)
UploadData(context, data, size);
}
} // namespace dp

View file

@ -0,0 +1,17 @@
#pragma once
#include "drape/data_buffer.hpp"
namespace dp
{
class IndexBuffer : public DataBuffer
{
public:
explicit IndexBuffer(uint32_t capacity);
// Check size of buffer and size of uploaded data.
void UploadData(ref_ptr<GraphicsContext> context, void const * data, uint32_t size);
// Resize buffer to new size, and discard old data.
void UpdateData(ref_ptr<GraphicsContext> context, void const * data, uint32_t size);
};
} // namespace dp

View file

@ -0,0 +1,38 @@
#include "drape/index_buffer_mutator.hpp"
#include "drape/vertex_array_buffer.hpp"
#include <algorithm>
#include <cstring>
namespace dp
{
IndexBufferMutator::IndexBufferMutator(uint32_t baseSize)
{
m_buffer.Resize(baseSize);
}
void IndexBufferMutator::AppendIndexes(void const * indexes, uint32_t count)
{
uint32_t dstActiveSize = m_activeSize + count;
if (dstActiveSize > m_buffer.Size())
m_buffer.Resize(std::max(m_buffer.Size() * 2, dstActiveSize));
memcpy(m_buffer.GetRaw(m_activeSize), indexes, count * IndexStorage::SizeOfIndex());
m_activeSize = dstActiveSize;
}
void const * IndexBufferMutator::GetIndexes() const
{
return m_buffer.GetRawConst();
}
uint32_t IndexBufferMutator::GetIndexCount() const
{
return m_activeSize;
}
uint32_t IndexBufferMutator::GetCapacity() const
{
return m_buffer.Size();
}
} // namespace dp

View file

@ -0,0 +1,30 @@
#pragma once
#include "drape/index_storage.hpp"
#include "drape/pointers.hpp"
#include <cstdint>
namespace dp
{
class VertexArrayBuffer;
class IndexBufferMutator
{
public:
explicit IndexBufferMutator(uint32_t baseSize);
void AppendIndexes(void const * indexes, uint32_t count);
uint32_t GetCapacity() const;
private:
friend class VertexArrayBuffer;
void const * GetIndexes() const;
uint32_t GetIndexCount() const;
private:
IndexStorage m_buffer;
uint32_t m_activeSize = 0;
};
} // namespace dp

View file

@ -0,0 +1,70 @@
#include "drape/index_storage.hpp"
#include "drape/gl_functions.hpp"
#include <utility>
namespace dp
{
IndexStorage::IndexStorage() : m_size(0) {}
IndexStorage::IndexStorage(std::vector<uint32_t> && initial)
{
m_size = (uint32_t)initial.size();
if (IsSupported32bit())
{
m_storage = std::move(initial);
}
else
{
// We pack 2 uint16_t indices into single m_storage element.
// Every element of "initial" vector is a single index.
m_storage.resize(GetStorageSize(m_size));
for (size_t i = 0; i < initial.size(); i++)
{
uint16_t * ptr = reinterpret_cast<uint16_t *>(m_storage.data()) + i;
*ptr = (uint16_t)initial[i];
}
}
}
void IndexStorage::Resize(uint32_t size)
{
m_size = size;
m_storage.resize(GetStorageSize(m_size));
}
uint32_t IndexStorage::Size() const
{
return m_size;
}
void * IndexStorage::GetRaw(uint32_t offsetInElements)
{
if (IsSupported32bit())
return &m_storage[offsetInElements];
return reinterpret_cast<uint16_t *>(m_storage.data()) + offsetInElements;
}
void const * IndexStorage::GetRawConst() const
{
return static_cast<void const *>(m_storage.data());
}
bool IndexStorage::IsSupported32bit()
{
// We do not use 32-bit indices now to reduce size of index buffers.
static bool const supports32Bit = false;
return supports32Bit;
}
uint32_t IndexStorage::SizeOfIndex()
{
return IsSupported32bit() ? sizeof(uint32_t) : sizeof(uint16_t);
}
uint32_t IndexStorage::GetStorageSize(uint32_t elementsCount) const
{
return IsSupported32bit() ? elementsCount : (elementsCount / 2 + 1);
}
} // namespace dp

View file

@ -0,0 +1,29 @@
#pragma once
#include <cstdint>
#include <vector>
namespace dp
{
class IndexStorage
{
public:
IndexStorage();
explicit IndexStorage(std::vector<uint32_t> && initial);
uint32_t Size() const;
void Resize(uint32_t size);
void * GetRaw(uint32_t offsetInElements = 0);
void const * GetRawConst() const;
static bool IsSupported32bit();
static uint32_t SizeOfIndex();
private:
std::vector<uint32_t> m_storage;
uint32_t m_size;
uint32_t GetStorageSize(uint32_t elementsCount) const;
};
} // namespace dp

327
libs/drape/mesh_object.cpp Normal file
View file

@ -0,0 +1,327 @@
#include "drape/mesh_object.hpp"
#include "drape/gl_constants.hpp"
#include "drape/gl_functions.hpp"
#include "drape/gl_gpu_program.hpp"
#include "drape/glsl_func.hpp"
#include "drape/glsl_types.hpp"
#include "drape/texture_manager.hpp"
#include "base/assert.hpp"
namespace
{
glConst GetGLDrawPrimitive(dp::MeshObject::DrawPrimitive drawPrimitive)
{
switch (drawPrimitive)
{
case dp::MeshObject::DrawPrimitive::Triangles: return gl_const::GLTriangles;
case dp::MeshObject::DrawPrimitive::TriangleStrip: return gl_const::GLTriangleStrip;
case dp::MeshObject::DrawPrimitive::LineStrip: return gl_const::GLLineStrip;
}
UNREACHABLE();
}
} // namespace
namespace dp
{
class GLMeshObjectImpl : public MeshObjectImpl
{
public:
explicit GLMeshObjectImpl(ref_ptr<dp::MeshObject> mesh) : m_mesh(mesh) {}
void Build(ref_ptr<dp::GraphicsContext> context, ref_ptr<dp::GpuProgram> program) override
{
UNUSED_VALUE(context);
m_VAO = GLFunctions::glGenVertexArray();
GLFunctions::glBindVertexArray(m_VAO);
for (auto & buffer : m_mesh->m_buffers)
{
buffer->m_bufferId = GLFunctions::glGenBuffer();
GLFunctions::glBindBuffer(buffer->m_bufferId, gl_const::GLArrayBuffer);
if (buffer->GetSizeInBytes() != 0)
{
GLFunctions::glBufferData(gl_const::GLArrayBuffer, buffer->GetSizeInBytes(), buffer->GetData(),
gl_const::GLStaticDraw);
}
if (!m_mesh->m_indices.empty())
{
m_indexBuffer = GLFunctions::glGenBuffer();
GLFunctions::glBindBuffer(m_indexBuffer, gl_const::GLElementArrayBuffer);
GLFunctions::glBufferData(gl_const::GLElementArrayBuffer,
static_cast<uint32_t>(m_mesh->m_indices.size() * sizeof(uint16_t)),
m_mesh->m_indices.data(), gl_const::GLStaticDraw);
}
ref_ptr<dp::GLGpuProgram> p = program;
for (auto const & attribute : buffer->m_attributes)
{
int8_t const attributePosition = p->GetAttributeLocation(attribute.m_attributeName);
ASSERT_NOT_EQUAL(attributePosition, -1, ());
GLFunctions::glEnableVertexAttribute(attributePosition);
GLFunctions::glVertexAttributePointer(attributePosition, attribute.m_componentsCount, attribute.m_type, false,
buffer->GetStrideInBytes(), attribute.m_offset);
}
}
GLFunctions::glBindVertexArray(0);
GLFunctions::glBindBuffer(0, gl_const::GLArrayBuffer);
if (!m_mesh->m_indices.empty())
GLFunctions::glBindBuffer(0, gl_const::GLElementArrayBuffer);
}
void Reset() override
{
for (auto & buffer : m_mesh->m_buffers)
{
if (buffer->m_bufferId != 0)
{
GLFunctions::glDeleteBuffer(buffer->m_bufferId);
buffer->m_bufferId = 0;
}
}
if (m_indexBuffer != 0)
{
GLFunctions::glDeleteBuffer(m_indexBuffer);
m_indexBuffer = 0;
}
if (m_VAO != 0)
{
GLFunctions::glDeleteVertexArray(m_VAO);
m_VAO = 0;
}
}
void UpdateBuffer(ref_ptr<dp::GraphicsContext> context, uint32_t bufferInd) override
{
UNUSED_VALUE(context);
auto & buffer = m_mesh->m_buffers[bufferInd];
GLFunctions::glBindBuffer(buffer->m_bufferId, gl_const::GLArrayBuffer);
GLFunctions::glBufferData(gl_const::GLArrayBuffer, buffer->GetSizeInBytes(), buffer->GetData(),
gl_const::GLStaticDraw);
GLFunctions::glBindBuffer(0, gl_const::GLArrayBuffer);
}
void UpdateIndexBuffer(ref_ptr<dp::GraphicsContext> context) override
{
UNUSED_VALUE(context);
CHECK(!m_mesh->m_indices.empty(), ());
CHECK(m_indexBuffer, ("Index buffer was not created"));
GLFunctions::glBindBuffer(m_indexBuffer, gl_const::GLElementArrayBuffer);
GLFunctions::glBufferData(gl_const::GLElementArrayBuffer,
static_cast<uint32_t>(m_mesh->m_indices.size() * sizeof(uint16_t)),
m_mesh->m_indices.data(), gl_const::GLStaticDraw);
GLFunctions::glBindBuffer(0, gl_const::GLElementArrayBuffer);
}
void Bind(ref_ptr<dp::GpuProgram> program) override { GLFunctions::glBindVertexArray(m_VAO); }
void Unbind() override
{
GLFunctions::glBindVertexArray(0);
GLFunctions::glBindBuffer(0, gl_const::GLArrayBuffer);
if (m_indexBuffer != 0)
GLFunctions::glBindBuffer(0, gl_const::GLElementArrayBuffer);
}
void DrawPrimitives(ref_ptr<dp::GraphicsContext> context, uint32_t verticesCount, uint32_t startVertex) override
{
UNUSED_VALUE(context);
GLFunctions::glDrawArrays(GetGLDrawPrimitive(m_mesh->m_drawPrimitive), static_cast<int32_t>(startVertex),
verticesCount);
}
void DrawPrimitivesIndexed(ref_ptr<dp::GraphicsContext> context, uint32_t indexCount, uint32_t startIndex) override
{
UNUSED_VALUE(context);
CHECK(m_indexBuffer != 0, ());
GLFunctions::glDrawElements(GetGLDrawPrimitive(m_mesh->m_drawPrimitive), sizeof(uint16_t), indexCount, startIndex);
}
private:
ref_ptr<dp::MeshObject> m_mesh;
uint32_t m_VAO = 0;
uint32_t m_indexBuffer = 0;
};
MeshObject::MeshObject(ref_ptr<dp::GraphicsContext> context, DrawPrimitive drawPrimitive, std::string const & debugName)
: m_drawPrimitive(drawPrimitive)
, m_debugName(debugName)
{
auto const apiVersion = context->GetApiVersion();
if (apiVersion == dp::ApiVersion::OpenGLES3)
{
InitForOpenGL();
}
else if (apiVersion == dp::ApiVersion::Metal)
{
#if defined(OMIM_METAL_AVAILABLE)
InitForMetal();
#endif
}
else if (apiVersion == dp::ApiVersion::Vulkan)
{
InitForVulkan(context);
}
CHECK(m_impl != nullptr, ());
}
MeshObject::~MeshObject()
{
Reset();
}
void MeshObject::InitForOpenGL()
{
m_impl = make_unique_dp<GLMeshObjectImpl>(make_ref(this));
}
void MeshObject::SetAttribute(std::string const & attributeName, uint32_t bufferInd, uint32_t offset,
uint32_t componentsCount, glConst type)
{
CHECK_LESS(bufferInd, m_buffers.size(), ());
CHECK(m_buffers[bufferInd], ());
m_buffers[bufferInd]->m_attributes.emplace_back(attributeName, offset, componentsCount, type);
Reset();
}
void MeshObject::Reset()
{
CHECK(m_impl != nullptr, ());
m_impl->Reset();
m_initialized = false;
}
void MeshObject::Build(ref_ptr<dp::GraphicsContext> context, ref_ptr<dp::GpuProgram> program)
{
Reset();
CHECK(m_impl != nullptr, ());
m_impl->Build(context, program);
m_initialized = true;
}
void MeshObject::Bind(ref_ptr<dp::GraphicsContext> context, ref_ptr<dp::GpuProgram> program)
{
program->Bind();
if (!m_initialized)
Build(context, program);
CHECK(m_impl != nullptr, ());
m_impl->Bind(program);
}
void MeshObject::SetIndexBuffer(std::vector<uint16_t> && indices)
{
m_indices = std::move(indices);
Reset();
}
void MeshObject::UpdateIndexBuffer(ref_ptr<dp::GraphicsContext> context, std::vector<uint16_t> const & indices)
{
CHECK(!indices.empty(), ("Use SetIndexBuffer() to reset index buffer"));
CHECK_LESS_OR_EQUAL(indices.size(), m_indices.size(), ());
memcpy(m_indices.data(), indices.data(), indices.size() * sizeof(uint16_t));
CHECK(m_impl != nullptr, ());
m_impl->UpdateIndexBuffer(context);
}
void MeshObject::DrawPrimitivesSubset(ref_ptr<dp::GraphicsContext> context, uint32_t vertexCount, uint32_t startVertex)
{
CHECK(m_impl != nullptr, ());
CHECK(!m_buffers.empty(), ());
auto const & buffer = m_buffers[0];
auto const vertexNum = buffer->GetSizeInBytes() / buffer->GetStrideInBytes();
CHECK_LESS(startVertex, vertexNum, ());
CHECK_LESS_OR_EQUAL(startVertex + vertexCount, vertexNum, ());
m_impl->DrawPrimitives(context, vertexCount, startVertex);
}
void MeshObject::DrawPrimitivesSubsetIndexed(ref_ptr<dp::GraphicsContext> context, uint32_t indexCount,
uint32_t startIndex)
{
CHECK(m_impl != nullptr, ());
CHECK(!m_indices.empty(), ());
CHECK_LESS(startIndex, m_indices.size(), ());
CHECK_LESS_OR_EQUAL(startIndex + indexCount, m_indices.size(), ());
m_impl->DrawPrimitivesIndexed(context, indexCount, startIndex);
}
void MeshObject::DrawPrimitives(ref_ptr<dp::GraphicsContext> context)
{
if (m_buffers.empty())
return;
auto const & buffer = m_buffers[0];
auto const vertexNum = buffer->GetSizeInBytes() / buffer->GetStrideInBytes();
#ifdef DEBUG
for (size_t i = 1; i < m_buffers.size(); i++)
{
ASSERT_EQUAL(m_buffers[i]->GetSizeInBytes() / m_buffers[i]->GetStrideInBytes(), vertexNum,
("All buffers in a mesh must contain the same vertex number"));
}
#endif
CHECK(m_impl != nullptr, ());
if (m_indices.empty())
m_impl->DrawPrimitives(context, vertexNum, 0);
else
m_impl->DrawPrimitivesIndexed(context, static_cast<uint32_t>(m_indices.size()), 0);
}
void MeshObject::Unbind(ref_ptr<dp::GpuProgram> program)
{
program->Unbind();
CHECK(m_impl != nullptr, ());
m_impl->Unbind();
}
void MeshObject::UpdateImpl(ref_ptr<dp::GraphicsContext> context, uint32_t bufferInd)
{
CHECK(m_impl != nullptr, ());
m_impl->UpdateBuffer(context, bufferInd);
}
// static
std::vector<float> MeshObject::GenerateNormalsForTriangles(std::vector<float> const & vertices, size_t componentsCount)
{
auto const trianglesCount = vertices.size() / (3 * componentsCount);
std::vector<float> normals;
normals.reserve(trianglesCount * 9);
for (size_t triangle = 0; triangle < trianglesCount; ++triangle)
{
glsl::vec3 v[3];
for (size_t vertex = 0; vertex < 3; ++vertex)
{
size_t const offset = triangle * componentsCount * 3 + vertex * componentsCount;
v[vertex] = glsl::vec3(vertices[offset], vertices[offset + 1], vertices[offset + 2]);
}
glsl::vec3 normal = glsl::cross(glsl::vec3(v[1].x - v[0].x, v[1].y - v[0].y, v[1].z - v[0].z),
glsl::vec3(v[2].x - v[0].x, v[2].y - v[0].y, v[2].z - v[0].z));
normal = glsl::normalize(normal);
for (size_t vertex = 0; vertex < 3; ++vertex)
{
normals.push_back(normal.x);
normals.push_back(normal.y);
normals.push_back(normal.z);
}
}
return normals;
}
} // namespace dp

214
libs/drape/mesh_object.hpp Normal file
View file

@ -0,0 +1,214 @@
#pragma once
#include "drape/drape_global.hpp"
#include "drape/graphics_context.hpp"
#include "drape/pointers.hpp"
#include "drape/render_state.hpp"
#include <cstdint>
#include <functional>
#include <string>
#include <vector>
namespace dp
{
class GpuProgram;
class GraphicsContext;
class MeshObjectImpl;
namespace metal
{
class MetalMeshObjectImpl;
} // namespace metal
namespace vulkan
{
class VulkanMeshObjectImpl;
} // namespace vulkan
// This class implements a simple mesh object which does not use an index buffer.
// Use this class only for simple geometry.
class MeshObject
{
friend class MeshObjectImpl;
friend class GLMeshObjectImpl;
friend class metal::MetalMeshObjectImpl;
friend class vulkan::VulkanMeshObjectImpl;
public:
enum class DrawPrimitive : uint8_t
{
Triangles,
TriangleStrip,
LineStrip
};
MeshObject(ref_ptr<dp::GraphicsContext> context, DrawPrimitive drawPrimitive, std::string const & debugName = "");
virtual ~MeshObject();
template <typename T>
void SetBuffer(uint32_t bufferInd, std::vector<T> && vertices, uint32_t stride = 0)
{
CHECK_LESS_OR_EQUAL(bufferInd, GetNextBufferIndex(), ());
if (bufferInd == GetNextBufferIndex())
m_buffers.emplace_back(make_unique_dp<VertexBuffer<T>>(std::move(vertices), stride));
else
m_buffers[bufferInd] = make_unique_dp<VertexBuffer<T>>(std::move(vertices), stride);
Reset();
}
void SetAttribute(std::string const & attributeName, uint32_t bufferInd, uint32_t offset, uint32_t componentsCount,
glConst type = gl_const::GLFloatType);
template <typename T>
void UpdateBuffer(ref_ptr<dp::GraphicsContext> context, uint32_t bufferInd, std::vector<T> const & vertices)
{
CHECK(m_initialized, ());
CHECK_LESS(bufferInd, static_cast<uint32_t>(m_buffers.size()), ());
CHECK(!vertices.empty(), ());
auto & buffer = m_buffers[bufferInd];
CHECK_LESS_OR_EQUAL(static_cast<uint32_t>(vertices.size() * sizeof(T)), buffer->GetSizeInBytes(), ());
memcpy(buffer->GetData(), vertices.data(), vertices.size() * sizeof(T));
UpdateImpl(context, bufferInd);
}
void SetIndexBuffer(std::vector<uint16_t> && indices);
void UpdateIndexBuffer(ref_ptr<dp::GraphicsContext> context, std::vector<uint16_t> const & indices);
template <typename TParamsSetter, typename TParams>
void Render(ref_ptr<dp::GraphicsContext> context, ref_ptr<dp::GpuProgram> program, dp::RenderState const & state,
ref_ptr<TParamsSetter> paramsSetter, TParams const & params)
{
Bind(context, program);
ApplyState(context, program, state);
paramsSetter->Apply(context, program, params);
DrawPrimitives(context);
Unbind(program);
}
template <typename TParamsSetter, typename TParams>
void Render(ref_ptr<dp::GraphicsContext> context, ref_ptr<dp::GpuProgram> program, dp::RenderState const & state,
ref_ptr<TParamsSetter> paramsSetter, TParams const & params, std::function<void()> && drawCallback)
{
Bind(context, program);
ApplyState(context, program, state);
paramsSetter->Apply(context, program, params);
CHECK(drawCallback, ());
drawCallback();
Unbind(program);
}
uint32_t GetNextBufferIndex() const { return static_cast<uint32_t>(m_buffers.size()); }
bool IsInitialized() const { return m_initialized; }
void Build(ref_ptr<dp::GraphicsContext> context, ref_ptr<dp::GpuProgram> program);
void Reset();
// Should be called inside draw callback in Render() method
void DrawPrimitivesSubset(ref_ptr<dp::GraphicsContext> context, uint32_t vertexCount, uint32_t startVertex);
void DrawPrimitivesSubsetIndexed(ref_ptr<dp::GraphicsContext> context, uint32_t indexCount, uint32_t startIndex);
static std::vector<float> GenerateNormalsForTriangles(std::vector<float> const & vertices, size_t componentsCount);
private:
struct AttributeMapping
{
AttributeMapping() = default;
AttributeMapping(std::string const & attributeName, uint32_t offset, uint32_t componentsCount,
glConst type = gl_const::GLFloatType)
: m_attributeName(attributeName)
, m_offset(offset)
, m_componentsCount(componentsCount)
, m_type(type)
{}
std::string m_attributeName;
uint32_t m_offset = 0;
uint32_t m_componentsCount = 0;
glConst m_type = gl_const::GLFloatType;
};
class VertexBufferBase
{
public:
virtual ~VertexBufferBase() = default;
virtual void * GetData() = 0;
virtual uint32_t GetSizeInBytes() const = 0;
virtual uint32_t GetStrideInBytes() const = 0;
uint32_t m_bufferId = 0;
std::vector<AttributeMapping> m_attributes;
};
template <typename T>
class VertexBuffer : public VertexBufferBase
{
public:
VertexBuffer() = default;
VertexBuffer(std::vector<T> && data, uint32_t stride = 0)
: m_data(std::move(data))
, m_stride(stride == 0 ? sizeof(T) : stride)
{
CHECK_GREATER_OR_EQUAL(m_stride, sizeof(T), ());
}
void * GetData() override { return m_data.data(); }
uint32_t GetSizeInBytes() const override { return static_cast<uint32_t>(m_data.size() * sizeof(T)); }
uint32_t GetStrideInBytes() const override { return m_stride; }
private:
std::vector<T> m_data;
uint32_t m_stride = 0;
};
void InitForOpenGL();
void InitForVulkan(ref_ptr<dp::GraphicsContext> context);
#if defined(OMIM_METAL_AVAILABLE)
// Definition of this method is in a .mm-file.
void InitForMetal();
#endif
void UpdateImpl(ref_ptr<dp::GraphicsContext> context, uint32_t bufferInd);
void Bind(ref_ptr<dp::GraphicsContext> context, ref_ptr<dp::GpuProgram> program);
void Unbind(ref_ptr<dp::GpuProgram> program);
void DrawPrimitives(ref_ptr<dp::GraphicsContext> context);
std::vector<drape_ptr<VertexBufferBase>> m_buffers;
std::vector<uint16_t> m_indices;
DrawPrimitive m_drawPrimitive = DrawPrimitive::Triangles;
drape_ptr<MeshObjectImpl> m_impl;
bool m_initialized = false;
std::string m_debugName;
};
class MeshObjectImpl
{
public:
virtual ~MeshObjectImpl() = default;
virtual void Build(ref_ptr<dp::GraphicsContext> context, ref_ptr<dp::GpuProgram> program) = 0;
virtual void Reset() = 0;
virtual void UpdateBuffer(ref_ptr<dp::GraphicsContext> context, uint32_t bufferInd) = 0;
virtual void UpdateIndexBuffer(ref_ptr<dp::GraphicsContext> context) = 0;
virtual void Bind(ref_ptr<dp::GpuProgram> program) = 0;
virtual void Unbind() = 0;
virtual void DrawPrimitives(ref_ptr<dp::GraphicsContext> context, uint32_t verticesCount, uint32_t startVertex) = 0;
virtual void DrawPrimitivesIndexed(ref_ptr<dp::GraphicsContext> context, uint32_t indexCount,
uint32_t startIndex) = 0;
};
} // namespace dp

View file

@ -0,0 +1,106 @@
#pragma once
#import <MetalKit/MetalKit.h>
#include "drape/gpu_program.hpp"
#include "drape/graphics_context.hpp"
#include "drape/metal/metal_cleaner.hpp"
#include "drape/metal/metal_states.hpp"
#include "drape/metal/metal_texture.hpp"
#include "drape/pointers.hpp"
#include "drape/texture_types.hpp"
#include "geometry/point2d.hpp"
#include <cstdint>
#include <functional>
namespace dp
{
namespace metal
{
class MetalBaseContext : public dp::GraphicsContext
{
public:
using DrawableRequest = std::function<id<CAMetalDrawable>()>;
MetalBaseContext(id<MTLDevice> device, m2::PointU const & screenSize, DrawableRequest && drawableRequest);
bool BeginRendering() override;
void EndRendering() override;
void Present() override;
void MakeCurrent() override {}
void DoneCurrent() override {}
bool Validate() override { return true; }
void Resize(uint32_t w, uint32_t h) override;
void SetFramebuffer(ref_ptr<dp::BaseFramebuffer> framebuffer) override;
void ForgetFramebuffer(ref_ptr<dp::BaseFramebuffer> framebuffer) override {}
void ApplyFramebuffer(std::string const & framebufferLabel) override;
void Init(ApiVersion apiVersion) override;
ApiVersion GetApiVersion() const override;
std::string GetRendererName() const override;
std::string GetRendererVersion() const override;
void DebugSynchronizeWithCPU() override;
void PushDebugLabel(std::string const & label) override;
void PopDebugLabel() override;
void SetClearColor(Color const & color) override;
void Clear(uint32_t clearBits, uint32_t storeBits) override;
void Flush() override {}
void SetViewport(uint32_t x, uint32_t y, uint32_t w, uint32_t h) override;
void SetScissor(uint32_t x, uint32_t y, uint32_t w, uint32_t h) override;
void SetDepthTestEnabled(bool enabled) override;
void SetDepthTestFunction(TestFunction depthFunction) override;
void SetStencilTestEnabled(bool enabled) override;
void SetStencilFunction(StencilFace face, TestFunction stencilFunction) override;
void SetStencilActions(StencilFace face, StencilAction stencilFailAction, StencilAction depthFailAction,
StencilAction passAction) override;
void SetStencilReferenceValue(uint32_t stencilReferenceValue) override
{
m_stencilReferenceValue = stencilReferenceValue;
}
void SetCullingEnabled(bool enabled) override;
id<MTLDevice> GetMetalDevice() const;
id<MTLCommandBuffer> GetCommandBuffer() const;
id<MTLRenderCommandEncoder> GetCommandEncoder() const;
id<MTLDepthStencilState> GetDepthStencilState();
id<MTLRenderPipelineState> GetPipelineState(ref_ptr<GpuProgram> program, bool blendingEnabled);
id<MTLSamplerState> GetSamplerState(TextureFilter filter, TextureWrapping wrapSMode, TextureWrapping wrapTMode);
void SetSystemPrograms(drape_ptr<GpuProgram> && programClearColor, drape_ptr<GpuProgram> && programClearDepth,
drape_ptr<GpuProgram> && programClearColorAndDepth);
void ApplyPipelineState(id<MTLRenderPipelineState> state);
bool HasAppliedPipelineState() const;
void ResetPipelineStatesCache();
MTLRenderPassDescriptor * GetRenderPassDescriptor() const;
protected:
void RecreateDepthTexture(m2::PointU const & screenSize);
void RequestFrameDrawable();
void ResetFrameDrawable();
void FinishCurrentEncoding();
id<MTLDevice> m_device;
DrawableRequest m_drawableRequest;
drape_ptr<MetalTexture> m_depthTexture;
MTLRenderPassDescriptor * m_renderPassDescriptor;
id<MTLCommandQueue> m_commandQueue;
ref_ptr<dp::BaseFramebuffer> m_currentFramebuffer;
MetalStates::DepthStencilKey m_currentDepthStencilKey;
MetalStates m_metalStates;
// These objects are recreated each frame. They MUST NOT be stored anywhere.
id<CAMetalDrawable> m_frameDrawable;
id<MTLCommandBuffer> m_frameCommandBuffer;
id<MTLRenderCommandEncoder> m_currentCommandEncoder;
id<MTLRenderPipelineState> m_lastPipelineState;
MetalCleaner m_cleaner;
uint32_t m_stencilReferenceValue = 1;
};
} // namespace metal
} // namespace dp

View file

@ -0,0 +1,462 @@
#include "drape/metal/metal_base_context.hpp"
#include "drape/metal/metal_texture.hpp"
#include "drape/framebuffer.hpp"
#include "base/assert.hpp"
#include "std/target_os.hpp"
#include <algorithm>
#include <functional>
#include <string>
#include <vector>
#include <utility>
namespace dp
{
namespace metal
{
MetalBaseContext::MetalBaseContext(id<MTLDevice> device, m2::PointU const & screenSize,
DrawableRequest && drawableRequest)
: m_device(device)
, m_drawableRequest(std::move(drawableRequest))
{
m_renderPassDescriptor = [MTLRenderPassDescriptor renderPassDescriptor];
m_renderPassDescriptor.colorAttachments[0].loadAction = MTLLoadActionClear;
m_renderPassDescriptor.colorAttachments[0].storeAction = MTLStoreActionStore;
m_renderPassDescriptor.depthAttachment.loadAction = MTLLoadActionClear;
m_renderPassDescriptor.depthAttachment.storeAction = MTLStoreActionStore;
m_renderPassDescriptor.depthAttachment.clearDepth = 1.0;
m_renderPassDescriptor.stencilAttachment.loadAction = MTLLoadActionClear;
m_renderPassDescriptor.stencilAttachment.storeAction = MTLStoreActionStore;
m_renderPassDescriptor.stencilAttachment.clearStencil = 0;
RecreateDepthTexture(screenSize);
}
void MetalBaseContext::RecreateDepthTexture(m2::PointU const & screenSize)
{
if (screenSize.x == 0 || screenSize.y == 0)
{
m_depthTexture.reset();
return;
}
m_depthTexture = make_unique_dp<MetalTexture>(nullptr /* allocator */);
HWTexture::Params params;
params.m_width = screenSize.x;
params.m_height = screenSize.y;
params.m_format = TextureFormat::Depth;
params.m_isRenderTarget = true;
m_depthTexture->Create(make_ref(this), params, nullptr /* data */);
}
void MetalBaseContext::Init(dp::ApiVersion apiVersion)
{
CHECK(apiVersion == dp::ApiVersion::Metal, ());
m_commandQueue = [m_device newCommandQueue];
}
ApiVersion MetalBaseContext::GetApiVersion() const
{
return dp::ApiVersion::Metal;
}
std::string MetalBaseContext::GetRendererName() const
{
return std::string([m_device.name UTF8String]);
}
std::string MetalBaseContext::GetRendererVersion() const
{
static std::vector<std::pair<MTLFeatureSet, std::string>> features;
if (features.empty())
{
#ifdef OMIM_OS_MAC
features.emplace_back(MTLFeatureSet_macOS_GPUFamily1_v1, "macOS_GPUFamily1_v1");
features.emplace_back(MTLFeatureSet_macOS_GPUFamily1_v2, "macOS_GPUFamily1_v2");
if (@available(macOS 10.13, *))
features.emplace_back(MTLFeatureSet_macOS_GPUFamily1_v3, "macOS_GPUFamily1_v3");
if (@available(macOS 10.14, *))
{
features.emplace_back(MTLFeatureSet_macOS_GPUFamily1_v4, "macOS_GPUFamily1_v4");
features.emplace_back(MTLFeatureSet_macOS_GPUFamily2_v1, "macOS_GPUFamily2_v1");
}
#else
features.emplace_back(MTLFeatureSet_iOS_GPUFamily1_v1, "iOS_GPUFamily1_v1");
features.emplace_back(MTLFeatureSet_iOS_GPUFamily2_v1, "iOS_GPUFamily2_v1");
features.emplace_back(MTLFeatureSet_iOS_GPUFamily1_v2, "iOS_GPUFamily1_v2");
features.emplace_back(MTLFeatureSet_iOS_GPUFamily2_v2, "iOS_GPUFamily2_v2");
features.emplace_back(MTLFeatureSet_iOS_GPUFamily3_v1, "iOS_GPUFamily3_v1");
features.emplace_back(MTLFeatureSet_iOS_GPUFamily1_v3, "iOS_GPUFamily1_v3");
features.emplace_back(MTLFeatureSet_iOS_GPUFamily2_v3, "iOS_GPUFamily2_v3");
features.emplace_back(MTLFeatureSet_iOS_GPUFamily3_v2, "iOS_GPUFamily3_v2");
features.emplace_back(MTLFeatureSet_iOS_GPUFamily1_v4, "iOS_GPUFamily1_v4");
features.emplace_back(MTLFeatureSet_iOS_GPUFamily2_v4, "iOS_GPUFamily2_v4");
features.emplace_back(MTLFeatureSet_iOS_GPUFamily3_v3, "iOS_GPUFamily3_v3");
features.emplace_back(MTLFeatureSet_iOS_GPUFamily4_v1, "iOS_GPUFamily4_v1");
features.emplace_back(MTLFeatureSet_iOS_GPUFamily1_v5, "iOS_GPUFamily1_v5");
features.emplace_back(MTLFeatureSet_iOS_GPUFamily2_v5, "iOS_GPUFamily2_v5");
features.emplace_back(MTLFeatureSet_iOS_GPUFamily3_v4, "iOS_GPUFamily3_v4");
features.emplace_back(MTLFeatureSet_iOS_GPUFamily4_v2, "iOS_GPUFamily4_v2");
features.emplace_back(MTLFeatureSet_iOS_GPUFamily5_v1, "iOS_GPUFamily5_v1");
#endif
std::sort(features.begin(), features.end(), [](auto const & s1, auto const & s2)
{
return s1.first > s2.first;
});
}
for (auto featureSet : features)
{
if ([m_device supportsFeatureSet:featureSet.first])
return featureSet.second;
}
return "Unknown";
}
void MetalBaseContext::PushDebugLabel(std::string const & label)
{
if (m_currentCommandEncoder == nil)
return;
[m_currentCommandEncoder pushDebugGroup:@(label.c_str())];
}
void MetalBaseContext::PopDebugLabel()
{
if (m_currentCommandEncoder == nil)
return;
[m_currentCommandEncoder popDebugGroup];
}
void MetalBaseContext::Resize(uint32_t w, uint32_t h)
{
if (m_depthTexture && m_depthTexture->GetWidth() == w && m_depthTexture->GetHeight() == h)
return;
RecreateDepthTexture({w, h});
}
void MetalBaseContext::SetFramebuffer(ref_ptr<dp::BaseFramebuffer> framebuffer)
{
FinishCurrentEncoding();
m_currentFramebuffer = framebuffer;
}
void MetalBaseContext::ApplyFramebuffer(std::string const & framebufferLabel)
{
// Initialize frame command buffer if there is no one.
if (!m_frameCommandBuffer)
{
m_frameCommandBuffer = [m_commandQueue commandBuffer];
m_frameCommandBuffer.label = @"Frame command buffer";
}
if (!m_currentFramebuffer)
{
// Use default(system) framebuffer and depth-stencil.
RequestFrameDrawable();
m_renderPassDescriptor.colorAttachments[0].texture = m_frameDrawable != nil ? m_frameDrawable.texture : nil;
m_renderPassDescriptor.depthAttachment.texture = m_depthTexture ? m_depthTexture->GetTexture() : nil;
m_renderPassDescriptor.stencilAttachment.texture = nil;
}
else
{
ref_ptr<Framebuffer> framebuffer = m_currentFramebuffer;
ASSERT(dynamic_cast<MetalTexture *>(framebuffer->GetTexture()->GetHardwareTexture().get()) != nullptr, ());
ref_ptr<MetalTexture> colorAttachment = framebuffer->GetTexture()->GetHardwareTexture();
m_renderPassDescriptor.colorAttachments[0].texture = colorAttachment->GetTexture();
auto const depthStencilRef = framebuffer->GetDepthStencilRef();
if (depthStencilRef != nullptr)
{
ASSERT(dynamic_cast<MetalTexture *>(depthStencilRef->GetTexture()->GetHardwareTexture().get()) != nullptr, ());
ref_ptr<MetalTexture> depthStencilAttachment = depthStencilRef->GetTexture()->GetHardwareTexture();
m_renderPassDescriptor.depthAttachment.texture = depthStencilAttachment->GetTexture();
if (depthStencilAttachment->GetFormat() == dp::TextureFormat::DepthStencil)
m_renderPassDescriptor.stencilAttachment.texture = depthStencilAttachment->GetTexture();
else
m_renderPassDescriptor.stencilAttachment.texture = nil;
}
else
{
m_renderPassDescriptor.depthAttachment.texture = nil;
m_renderPassDescriptor.stencilAttachment.texture = nil;
}
}
CHECK(m_currentCommandEncoder == nil, ("Current command encoder was not finished."));
m_currentCommandEncoder = [m_frameCommandBuffer renderCommandEncoderWithDescriptor:m_renderPassDescriptor];
m_currentCommandEncoder.label = @(framebufferLabel.c_str());
[m_currentCommandEncoder pushDebugGroup:@(framebufferLabel.c_str())];
// Default rendering options.
[m_currentCommandEncoder setFrontFacingWinding:MTLWindingClockwise];
[m_currentCommandEncoder setCullMode:MTLCullModeBack];
[m_currentCommandEncoder setStencilReferenceValue:m_stencilReferenceValue];
}
void MetalBaseContext::SetClearColor(dp::Color const & color)
{
m_cleaner.SetClearColor(color);
m_renderPassDescriptor.colorAttachments[0].clearColor =
MTLClearColorMake(color.GetRedF(), color.GetGreenF(), color.GetBlueF(), color.GetAlphaF());
}
void MetalBaseContext::Clear(uint32_t clearBits, uint32_t storeBits)
{
if (m_currentCommandEncoder != nil)
{
if ((clearBits & ClearBits::ColorBit) && (clearBits & ClearBits::DepthBit))
m_cleaner.ClearColorAndDepth(make_ref(this), m_currentCommandEncoder);
else if (clearBits & ClearBits::ColorBit)
m_cleaner.ClearColor(make_ref(this), m_currentCommandEncoder);
else if (clearBits & ClearBits::DepthBit)
m_cleaner.ClearDepth(make_ref(this), m_currentCommandEncoder);
if (clearBits & ClearBits::StencilBit)
CHECK(false, ("Stencil clearing is not implemented"));
}
else
{
// Here, if we do not clear attachments, we load data ONLY if we store it afterwards, otherwise we use 'DontCare' option
// to improve performance.
if (clearBits & ClearBits::ColorBit)
m_renderPassDescriptor.colorAttachments[0].loadAction = MTLLoadActionClear;
else
m_renderPassDescriptor.colorAttachments[0].loadAction = (storeBits & ClearBits::ColorBit) ? MTLLoadActionLoad : MTLLoadActionDontCare;
if (clearBits & ClearBits::DepthBit)
m_renderPassDescriptor.depthAttachment.loadAction = MTLLoadActionClear;
else
m_renderPassDescriptor.depthAttachment.loadAction = (storeBits & ClearBits::DepthBit) ? MTLLoadActionLoad : MTLLoadActionDontCare;
if (clearBits & ClearBits::StencilBit)
m_renderPassDescriptor.stencilAttachment.loadAction = MTLLoadActionClear;
else
m_renderPassDescriptor.stencilAttachment.loadAction = (storeBits & ClearBits::StencilBit) ? MTLLoadActionLoad : MTLLoadActionDontCare;
// Apply storing mode.
if (storeBits & ClearBits::ColorBit)
m_renderPassDescriptor.colorAttachments[0].storeAction = MTLStoreActionStore;
else
m_renderPassDescriptor.colorAttachments[0].storeAction = MTLStoreActionDontCare;
if (storeBits & ClearBits::DepthBit)
m_renderPassDescriptor.depthAttachment.storeAction = MTLStoreActionStore;
else
m_renderPassDescriptor.depthAttachment.storeAction = MTLStoreActionDontCare;
if (storeBits & ClearBits::StencilBit)
m_renderPassDescriptor.stencilAttachment.storeAction = MTLStoreActionStore;
else
m_renderPassDescriptor.stencilAttachment.storeAction = MTLStoreActionDontCare;
}
}
void MetalBaseContext::SetViewport(uint32_t x, uint32_t y, uint32_t w, uint32_t h)
{
id<MTLRenderCommandEncoder> encoder = GetCommandEncoder();
[encoder setViewport:MTLViewport(x, y, w, h, 0.0, 1.0)];
[encoder setScissorRect:{x, y, w, h}];
}
void MetalBaseContext::SetScissor(uint32_t x, uint32_t y, uint32_t w, uint32_t h)
{
id<MTLRenderCommandEncoder> encoder = GetCommandEncoder();
if (m_renderPassDescriptor.colorAttachments[0].texture != nil)
{
auto const rpWidth = static_cast<uint32_t>(m_renderPassDescriptor.colorAttachments[0].texture.width);
auto const rpHeight = static_cast<uint32_t>(m_renderPassDescriptor.colorAttachments[0].texture.height);
if (x < 0)
x = 0;
if (y < 0)
y = 0;
if (x + w > rpWidth)
w = rpWidth - x;
if (y + h > rpHeight)
h = rpHeight - y;
[encoder setScissorRect:{x, y, w, h}];
}
}
void MetalBaseContext::SetDepthTestEnabled(bool enabled)
{
m_currentDepthStencilKey.m_depthEnabled = enabled;
}
void MetalBaseContext::SetDepthTestFunction(dp::TestFunction depthFunction)
{
m_currentDepthStencilKey.m_depthFunction = depthFunction;
}
void MetalBaseContext::SetStencilTestEnabled(bool enabled)
{
m_currentDepthStencilKey.m_stencilEnabled = enabled;
}
void MetalBaseContext::SetStencilFunction(dp::StencilFace face, dp::TestFunction stencilFunction)
{
m_currentDepthStencilKey.SetStencilFunction(face, stencilFunction);
}
void MetalBaseContext::SetStencilActions(dp::StencilFace face,
dp::StencilAction stencilFailAction,
dp::StencilAction depthFailAction,
dp::StencilAction passAction)
{
m_currentDepthStencilKey.SetStencilActions(face, stencilFailAction, depthFailAction, passAction);
}
void MetalBaseContext::SetCullingEnabled(bool enabled)
{
id<MTLRenderCommandEncoder> encoder = GetCommandEncoder();
[encoder setCullMode: (enabled ? MTLCullModeBack : MTLCullModeNone)];
}
id<MTLDevice> MetalBaseContext::GetMetalDevice() const
{
return m_device;
}
id<MTLRenderCommandEncoder> MetalBaseContext::GetCommandEncoder() const
{
CHECK(m_currentCommandEncoder != nil, ("Probably encoding commands were called before ApplyFramebuffer."));
return m_currentCommandEncoder;
}
id<MTLCommandBuffer> MetalBaseContext::GetCommandBuffer() const
{
CHECK(m_frameCommandBuffer != nil, ("Probably encoding commands were called before ApplyFramebuffer."));
return m_frameCommandBuffer;
}
id<MTLDepthStencilState> MetalBaseContext::GetDepthStencilState()
{
return m_metalStates.GetDepthStencilState(m_device, m_currentDepthStencilKey);
}
id<MTLRenderPipelineState> MetalBaseContext::GetPipelineState(ref_ptr<GpuProgram> program, bool blendingEnabled)
{
CHECK(m_currentCommandEncoder != nil, ("Probably encoding commands were called before ApplyFramebuffer."));
id<MTLTexture> colorTexture = m_renderPassDescriptor.colorAttachments[0].texture;
// It can be nil in the case when Metal drawable is absent (e.g. finish rendering in background).
if (colorTexture == nil)
return nil;
id<MTLTexture> depthTexture = m_renderPassDescriptor.depthAttachment.texture;
MTLPixelFormat depthStencilFormat = (depthTexture != nil) ? depthTexture.pixelFormat : MTLPixelFormatInvalid;
MetalStates::PipelineKey const key(program, colorTexture.pixelFormat, depthStencilFormat, blendingEnabled);
return m_metalStates.GetPipelineState(m_device, key);
}
id<MTLSamplerState> MetalBaseContext::GetSamplerState(TextureFilter filter, TextureWrapping wrapSMode,
TextureWrapping wrapTMode)
{
MetalStates::SamplerKey const key(filter, wrapSMode, wrapTMode);
return m_metalStates.GetSamplerState(m_device, key);
}
bool MetalBaseContext::BeginRendering()
{
CHECK(m_currentCommandEncoder == nil, ("Current command encoder was not finished."));
return true;
}
void MetalBaseContext::EndRendering()
{
FinishCurrentEncoding();
}
void MetalBaseContext::Present()
{
RequestFrameDrawable();
if (m_frameDrawable)
[m_frameCommandBuffer presentDrawable:m_frameDrawable];
[m_frameCommandBuffer commit];
m_frameDrawable = nil;
[m_frameCommandBuffer waitUntilCompleted];
m_frameCommandBuffer = nil;
}
void MetalBaseContext::RequestFrameDrawable()
{
if (m_frameDrawable != nil)
return;
CHECK(m_drawableRequest != nullptr, ());
m_frameDrawable = m_drawableRequest();
}
void MetalBaseContext::ResetFrameDrawable()
{
if (m_frameDrawable == nil)
return;
m_frameDrawable = nil;
RequestFrameDrawable();
}
void MetalBaseContext::FinishCurrentEncoding()
{
[m_currentCommandEncoder popDebugGroup];
[m_currentCommandEncoder endEncoding];
m_currentCommandEncoder = nil;
m_lastPipelineState = nil;
}
void MetalBaseContext::SetSystemPrograms(drape_ptr<GpuProgram> && programClearColor,
drape_ptr<GpuProgram> && programClearDepth,
drape_ptr<GpuProgram> && programClearColorAndDepth)
{
m_cleaner.Init(make_ref(this), std::move(programClearColor), std::move(programClearDepth),
std::move(programClearColorAndDepth));
}
void MetalBaseContext::ApplyPipelineState(id<MTLRenderPipelineState> state)
{
m_lastPipelineState = state;
if (state != nil)
[GetCommandEncoder() setRenderPipelineState:state];
}
bool MetalBaseContext::HasAppliedPipelineState() const
{
return m_lastPipelineState != nil;
}
void MetalBaseContext::ResetPipelineStatesCache()
{
m_metalStates.ResetPipelineStatesCache();
}
void MetalBaseContext::DebugSynchronizeWithCPU()
{
FinishCurrentEncoding();
RequestFrameDrawable();
[m_frameCommandBuffer commit];
m_frameDrawable = nil;
[m_frameCommandBuffer waitUntilCompleted];
m_frameCommandBuffer = nil;
}
MTLRenderPassDescriptor * MetalBaseContext::GetRenderPassDescriptor() const
{
return m_renderPassDescriptor;
}
} // namespace metal
void RenderFrameMediator(std::function<void()> && renderFrameFunction)
{
@autoreleasepool
{
renderFrameFunction();
}
}
} // namespace dp

View file

@ -0,0 +1,45 @@
#pragma once
#import <MetalKit/MetalKit.h>
#include "drape/color.hpp"
#include "drape/glsl_types.hpp"
#include "drape/pointers.hpp"
namespace dp
{
class GpuProgram;
namespace metal
{
class MetalBaseContext;
class MetalCleaner
{
public:
MetalCleaner() = default;
void Init(ref_ptr<MetalBaseContext> context, drape_ptr<GpuProgram> && programClearColor,
drape_ptr<GpuProgram> && programClearDepth, drape_ptr<GpuProgram> && programClearColorAndDepth);
void SetClearColor(Color const & color);
void ClearDepth(ref_ptr<MetalBaseContext> context, id<MTLRenderCommandEncoder> encoder);
void ClearColor(ref_ptr<MetalBaseContext> context, id<MTLRenderCommandEncoder> encoder);
void ClearColorAndDepth(ref_ptr<MetalBaseContext> context, id<MTLRenderCommandEncoder> encoder);
private:
void ApplyColorParam(id<MTLRenderCommandEncoder> encoder, ref_ptr<GpuProgram> program);
void RenderQuad(ref_ptr<MetalBaseContext> context, id<MTLRenderCommandEncoder> encoder, ref_ptr<GpuProgram> program);
id<MTLBuffer> m_buffer;
id<MTLDepthStencilState> m_depthEnabledState;
id<MTLDepthStencilState> m_depthDisabledState;
glsl::vec4 m_clearColor;
drape_ptr<GpuProgram> m_programClearColor;
drape_ptr<GpuProgram> m_programClearDepth;
drape_ptr<GpuProgram> m_programClearColorAndDepth;
};
} // namespace metal
} // namespace dp

View file

@ -0,0 +1,95 @@
#include "drape/metal/metal_cleaner.hpp"
#include "drape/metal/metal_base_context.hpp"
#include "drape/metal/metal_gpu_program.hpp"
#include <vector>
namespace dp
{
namespace metal
{
void MetalCleaner::Init(ref_ptr<MetalBaseContext> context,
drape_ptr<GpuProgram> && programClearColor,
drape_ptr<GpuProgram> && programClearDepth,
drape_ptr<GpuProgram> && programClearColorAndDepth)
{
m_programClearColor = std::move(programClearColor);
m_programClearDepth = std::move(programClearDepth);
m_programClearColorAndDepth = std::move(programClearColorAndDepth);
ref_ptr<MetalBaseContext> metalContext = context;
id<MTLDevice> device = metalContext->GetMetalDevice();
std::vector<float> quad = {-1.0f, 1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f};
m_buffer = [device newBufferWithBytes:quad.data()
length:quad.size() * sizeof(quad[0])
options:MTLResourceCPUCacheModeWriteCombined];
m_buffer.label = @"MetalCleaner";
MTLDepthStencilDescriptor * desc = [[MTLDepthStencilDescriptor alloc] init];
desc.depthWriteEnabled = YES;
desc.depthCompareFunction = MTLCompareFunctionAlways;
m_depthEnabledState = [device newDepthStencilStateWithDescriptor:desc];
CHECK(m_depthEnabledState != nil, ());
desc.depthWriteEnabled = NO;
desc.depthCompareFunction = MTLCompareFunctionAlways;
m_depthDisabledState = [device newDepthStencilStateWithDescriptor:desc];
CHECK(m_depthDisabledState != nil, ());
}
void MetalCleaner::SetClearColor(Color const & color)
{
m_clearColor = glsl::ToVec4(color);
}
void MetalCleaner::ApplyColorParam(id<MTLRenderCommandEncoder> encoder, ref_ptr<GpuProgram> program)
{
ref_ptr<MetalGpuProgram> metalProgram = program;
auto const fsBindingIndex = metalProgram->GetFragmentShaderUniformsBindingIndex();
if (fsBindingIndex >= 0)
{
[encoder setFragmentBytes:(void const *)&m_clearColor length:sizeof(m_clearColor)
atIndex:fsBindingIndex];
}
}
void MetalCleaner::RenderQuad(ref_ptr<MetalBaseContext> metalContext, id<MTLRenderCommandEncoder> encoder,
ref_ptr<GpuProgram> program)
{
id<MTLRenderPipelineState> pipelineState = metalContext->GetPipelineState(program, false /* blendingEnabled */);
if (pipelineState == nil)
return;
[encoder setRenderPipelineState:pipelineState];
[encoder setVertexBuffer:m_buffer offset:0 atIndex:0];
[encoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
}
void MetalCleaner::ClearDepth(ref_ptr<MetalBaseContext> context, id<MTLRenderCommandEncoder> encoder)
{
[encoder pushDebugGroup:@"ClearDepth"];
[encoder setDepthStencilState:m_depthEnabledState];
RenderQuad(context, encoder, make_ref(m_programClearDepth));
[encoder popDebugGroup];
}
void MetalCleaner::ClearColor(ref_ptr<MetalBaseContext> context, id<MTLRenderCommandEncoder> encoder)
{
[encoder pushDebugGroup:@"ClearColor"];
[encoder setDepthStencilState:m_depthDisabledState];
ApplyColorParam(encoder, make_ref(m_programClearColor));
RenderQuad(context, encoder, make_ref(m_programClearColor));
[encoder popDebugGroup];
}
void MetalCleaner::ClearColorAndDepth(ref_ptr<MetalBaseContext> context, id<MTLRenderCommandEncoder> encoder)
{
[encoder pushDebugGroup:@"ClearColorAndDepth"];
[encoder setDepthStencilState:m_depthEnabledState];
ApplyColorParam(encoder, make_ref(m_programClearColorAndDepth));
RenderQuad(context, encoder, make_ref(m_programClearColorAndDepth));
[encoder popDebugGroup];
}
} // namespace metal
} // namespace dp

View file

@ -0,0 +1,73 @@
#pragma once
#import <MetalKit/MetalKit.h>
#include "drape/data_buffer.hpp"
#include "drape/data_buffer_impl.hpp"
#include "drape/metal/metal_base_context.hpp"
#include "drape/pointers.hpp"
#include "base/assert.hpp"
#include <cstdint>
#include <utility>
namespace dp
{
namespace metal
{
class MetalGPUBuffer : public BufferBase
{
public:
MetalGPUBuffer(ref_ptr<GraphicsContext> context, void const * data, uint8_t elementSize, uint32_t capacity);
void UploadData(void const * data, uint32_t elementCount);
void * Map(uint32_t elementOffset, uint32_t elementCount);
void UpdateData(void * gpuPtr, void const * data, uint32_t elementOffset, uint32_t elementCount);
id<MTLBuffer> GetMetalBuffer() const { return m_metalBuffer; }
protected:
void Resize(ref_ptr<MetalBaseContext> context, void const * data, uint32_t elementCount);
id<MTLBuffer> m_metalBuffer;
};
class MetalGpuBufferImpl : public DataBufferImpl<MetalGPUBuffer>
{
public:
template <typename... Args>
MetalGpuBufferImpl(Args &&... params) : DataBufferImpl(std::forward<Args>(params)...)
{}
void const * Data() const override
{
ASSERT(false, ("Retrieving of raw data is unavailable for GPU buffer"));
return nullptr;
}
void UploadData(ref_ptr<GraphicsContext> context, void const * data, uint32_t elementCount) override
{
UNUSED_VALUE(context);
m_buffer->UploadData(data, elementCount);
}
void UpdateData(void * destPtr, void const * srcPtr, uint32_t elementOffset, uint32_t elementCount) override
{
m_buffer->UpdateData(destPtr, srcPtr, elementOffset, elementCount);
}
void * Map(ref_ptr<GraphicsContext> context, uint32_t elementOffset, uint32_t elementCount) override
{
UNUSED_VALUE(context);
return m_buffer->Map(elementOffset, elementCount);
}
id<MTLBuffer> GetMetalBuffer() const { return m_buffer->GetMetalBuffer(); }
void Bind() override {}
void Unmap(ref_ptr<GraphicsContext>) override {}
};
} // namespace metal
} // namespace dp

View file

@ -0,0 +1,81 @@
#include "drape/metal/metal_gpu_buffer_impl.hpp"
#include "base/macros.hpp"
#include <cstring>
namespace dp
{
namespace metal
{
MetalGPUBuffer::MetalGPUBuffer(ref_ptr<GraphicsContext> context, void const * data,
uint8_t elementSize, uint32_t capacity)
: BufferBase(elementSize, capacity)
{
Resize(context, data, capacity);
}
void MetalGPUBuffer::UploadData(void const * data, uint32_t elementCount)
{
uint32_t const currentSize = GetCurrentSize();
uint8_t const elementSize = GetElementSize();
ASSERT(GetCapacity() >= elementCount + currentSize,
("Not enough memory to upload ", elementCount, " elements"));
uint32_t const byteOffset = currentSize * elementSize;
uint32_t const sizeInBytes = elementCount * elementSize;
uint8_t * gpuPtr = static_cast<uint8_t *>([m_metalBuffer contents]) + byteOffset;
memcpy(gpuPtr, data, sizeInBytes);
BufferBase::UploadData(elementCount);
}
void * MetalGPUBuffer::Map(uint32_t elementOffset, uint32_t elementCount)
{
UNUSED_VALUE(elementCount);
uint32_t const elementSize = GetElementSize();
uint32_t const byteOffset = elementOffset * elementSize;
uint8_t * bufferPointer = static_cast<uint8_t *>([m_metalBuffer contents]) + byteOffset;
return bufferPointer;
}
void MetalGPUBuffer::UpdateData(void * gpuPtr, void const * data,
uint32_t elementOffset, uint32_t elementCount)
{
uint32_t const elementSize = GetElementSize();
uint32_t const byteOffset = elementOffset * elementSize;
uint32_t const byteCount = elementCount * elementSize;
ASSERT(gpuPtr != nullptr, ());
memcpy((uint8_t *)gpuPtr + byteOffset, data, byteCount);
}
void MetalGPUBuffer::Resize(ref_ptr<MetalBaseContext> context, void const * data, uint32_t elementCount)
{
BufferBase::Resize(elementCount);
id<MTLDevice> device = context->GetMetalDevice();
uint32_t const sizeInBytes = GetCapacity() * GetElementSize();
if (data != nil)
{
m_metalBuffer = [device newBufferWithBytes:data
length:sizeInBytes
options:MTLResourceCPUCacheModeWriteCombined];
}
else
{
m_metalBuffer = [device newBufferWithLength:sizeInBytes
options:MTLResourceCPUCacheModeWriteCombined];
}
// If we have already set up data, we have to call SetDataSize.
if (data != nullptr)
SetDataSize(elementCount);
}
} // namespace metal
drape_ptr<DataBufferBase> DataBuffer::CreateImplForMetal(ref_ptr<GraphicsContext> context, void const * data,
uint8_t elementSize, uint32_t capacity)
{
return make_unique_dp<metal::MetalGpuBufferImpl>(context, data, elementSize, capacity);
}
} // namespace dp

View file

@ -0,0 +1,81 @@
#pragma once
#import <MetalKit/MetalKit.h>
#include "drape/gpu_program.hpp"
#include <cstdint>
#include <map>
#include <string>
#include <utility>
namespace dp
{
namespace metal
{
class MetalGpuProgram : public GpuProgram
{
public:
static int8_t constexpr kInvalidBindingIndex = -1;
struct TextureBindingInfo
{
int8_t m_textureBindingIndex = kInvalidBindingIndex;
int8_t m_samplerBindingIndex = kInvalidBindingIndex;
};
using TexturesBindingInfo = std::map<std::string, TextureBindingInfo>;
MetalGpuProgram(std::string const & programName, id<MTLFunction> vertexShader, id<MTLFunction> fragmentShader,
int8_t vsUniformsBindingIndex, int8_t fsUniformsBindingIndex,
TexturesBindingInfo && vertexTextureBindingInfo, TexturesBindingInfo && fragmentTextureBindingInfo,
MTLVertexDescriptor * vertexDescriptor)
: GpuProgram(programName)
, m_vertexShader(vertexShader)
, m_fragmentShader(fragmentShader)
, m_vsUniformsBindingIndex(vsUniformsBindingIndex)
, m_fsUniformsBindingIndex(fsUniformsBindingIndex)
, m_vertexTextureBindingInfo(std::move(vertexTextureBindingInfo))
, m_fragmentTextureBindingInfo(std::move(fragmentTextureBindingInfo))
, m_vertexDescriptor(vertexDescriptor)
{}
void Bind() override {}
void Unbind() override {}
id<MTLFunction> GetVertexShader() const { return m_vertexShader; }
id<MTLFunction> GetFragmentShader() const { return m_fragmentShader; }
int8_t GetVertexShaderUniformsBindingIndex() const { return m_vsUniformsBindingIndex; }
int8_t GetFragmentShaderUniformsBindingIndex() const { return m_fsUniformsBindingIndex; }
TextureBindingInfo const & GetVertexTextureBindingInfo(std::string const & textureName) const
{
return GetTextureBindingInfo(m_vertexTextureBindingInfo, textureName);
}
TextureBindingInfo const & GetFragmentTextureBindingInfo(std::string const & textureName) const
{
return GetTextureBindingInfo(m_fragmentTextureBindingInfo, textureName);
}
MTLVertexDescriptor * GetVertexDescriptor() const { return m_vertexDescriptor; }
private:
TextureBindingInfo const & GetTextureBindingInfo(TexturesBindingInfo const & bindingInfo,
std::string const & textureName) const
{
static TextureBindingInfo kEmptyBinding;
auto const it = bindingInfo.find(textureName);
if (it == bindingInfo.cend())
return kEmptyBinding;
return it->second;
}
id<MTLFunction> m_vertexShader;
id<MTLFunction> m_fragmentShader;
int8_t const m_vsUniformsBindingIndex;
int8_t const m_fsUniformsBindingIndex;
TexturesBindingInfo const m_vertexTextureBindingInfo;
TexturesBindingInfo const m_fragmentTextureBindingInfo;
MTLVertexDescriptor * m_vertexDescriptor;
};
} // namespace metal
} // namespace dp

View file

@ -0,0 +1,156 @@
#import <MetalKit/MetalKit.h>
#include "drape/metal/metal_base_context.hpp"
#include "drape/mesh_object.hpp"
#include "drape/pointers.hpp"
#include "base/assert.hpp"
#include <cstdint>
#include <cstring>
#include <sstream>
namespace dp
{
namespace metal
{
namespace
{
MTLPrimitiveType GetPrimitiveType(MeshObject::DrawPrimitive primitive)
{
switch (primitive)
{
case MeshObject::DrawPrimitive::Triangles: return MTLPrimitiveTypeTriangle;
case MeshObject::DrawPrimitive::TriangleStrip: return MTLPrimitiveTypeTriangleStrip;
case MeshObject::DrawPrimitive::LineStrip: return MTLPrimitiveTypeLineStrip;
}
CHECK(false, ("Unsupported type"));
}
} // namespace
class MetalMeshObjectImpl : public MeshObjectImpl
{
public:
MetalMeshObjectImpl(ref_ptr<dp::MeshObject> mesh)
: m_mesh(std::move(mesh))
{}
void Build(ref_ptr<dp::GraphicsContext> context, ref_ptr<dp::GpuProgram> program) override
{
ref_ptr<dp::metal::MetalBaseContext> metalContext = context;
id<MTLDevice> device = metalContext->GetMetalDevice();
m_geometryBuffers.resize(m_mesh->m_buffers.size());
for (size_t i = 0; i < m_mesh->m_buffers.size(); i++)
{
auto const sizeInBytes = m_mesh->m_buffers[i]->GetSizeInBytes();
if (sizeInBytes == 0)
continue;
m_geometryBuffers[i] = [device newBufferWithBytes:m_mesh->m_buffers[i]->GetData()
length:sizeInBytes
options:MTLResourceCPUCacheModeWriteCombined];
std::ostringstream ss;
ss << "MeshVB:";
for (size_t j = 0; j < m_mesh->m_buffers[i]->m_attributes.size(); j++)
{
ss << m_mesh->m_buffers[i]->m_attributes[j].m_attributeName;
if (j + 1 < m_mesh->m_buffers[i]->m_attributes.size())
ss << "+";
}
m_geometryBuffers[i].label = @(ss.str().c_str());
}
if (!m_mesh->m_indices.empty())
{
m_indexBuffer = [device newBufferWithBytes:m_mesh->m_indices.data()
length:m_mesh->m_indices.size() * sizeof(uint16_t)
options:MTLResourceCPUCacheModeWriteCombined];
m_indexBuffer.label = @"MeshIB";
}
}
void Reset() override
{
m_geometryBuffers.clear();
m_indexBuffer = nil;
}
void UpdateBuffer(ref_ptr<dp::GraphicsContext> context, uint32_t bufferInd) override
{
UNUSED_VALUE(context);
CHECK_LESS(bufferInd, static_cast<uint32_t>(m_geometryBuffers.size()), ());
auto & buffer = m_mesh->m_buffers[bufferInd];
auto const sizeInBytes = buffer->GetSizeInBytes();
CHECK(sizeInBytes != 0, ());
uint8_t * bufferPointer = (uint8_t *)[m_geometryBuffers[bufferInd] contents];
memcpy(bufferPointer, buffer->GetData(), sizeInBytes);
}
void UpdateIndexBuffer(ref_ptr<dp::GraphicsContext> context) override
{
UNUSED_VALUE(context);
CHECK(m_indexBuffer != nil, ());
auto const sizeInBytes = m_mesh->m_indices.size() * sizeof(uint16_t);
CHECK(sizeInBytes != 0, ());
uint8_t * bufferPointer = (uint8_t *)[m_indexBuffer contents];
memcpy(bufferPointer, m_mesh->m_indices.data(), sizeInBytes);
}
void Bind(ref_ptr<dp::GpuProgram> program) override {}
void Unbind() override {}
void DrawPrimitives(ref_ptr<dp::GraphicsContext> context, uint32_t vertexCount,
uint32_t startVertex) override
{
ref_ptr<dp::metal::MetalBaseContext> metalContext = context;
if (!metalContext->HasAppliedPipelineState())
return;
id<MTLRenderCommandEncoder> encoder = metalContext->GetCommandEncoder();
for (size_t i = 0; i < m_geometryBuffers.size(); i++)
[encoder setVertexBuffer:m_geometryBuffers[i] offset:0 atIndex:i];
[encoder drawPrimitives:GetPrimitiveType(m_mesh->m_drawPrimitive)
vertexStart:startVertex
vertexCount:vertexCount];
}
void DrawPrimitivesIndexed(ref_ptr<dp::GraphicsContext> context, uint32_t indexCount,
uint32_t startIndex) override
{
ref_ptr<dp::metal::MetalBaseContext> metalContext = context;
if (!metalContext->HasAppliedPipelineState())
return;
id<MTLRenderCommandEncoder> encoder = metalContext->GetCommandEncoder();
for (size_t i = 0; i < m_geometryBuffers.size(); i++)
[encoder setVertexBuffer:m_geometryBuffers[i] offset:0 atIndex:i];
CHECK(m_indexBuffer != nil, ());
[encoder drawIndexedPrimitives:GetPrimitiveType(m_mesh->m_drawPrimitive)
indexCount:indexCount
indexType:MTLIndexTypeUInt16
indexBuffer:m_indexBuffer
indexBufferOffset:startIndex * sizeof(uint16_t)];
}
private:
ref_ptr<dp::MeshObject> m_mesh;
std::vector<id<MTLBuffer>> m_geometryBuffers;
id<MTLBuffer> m_indexBuffer;
};
} // namespace metal
#ifdef OMIM_METAL_AVAILABLE
void MeshObject::InitForMetal()
{
m_impl = make_unique_dp<metal::MetalMeshObjectImpl>(make_ref(this));
}
#endif // OMIM_METAL_AVAILABLE
} // namespace dp

View file

@ -0,0 +1,79 @@
#pragma once
#import <MetalKit/MetalKit.h>
#include "drape/graphics_context.hpp"
#include "drape/metal/metal_gpu_program.hpp"
#include "drape/pointers.hpp"
#include "drape/texture_types.hpp"
#include <cstdint>
#include <map>
namespace dp
{
namespace metal
{
class MetalStates
{
public:
struct DepthStencilKey
{
void SetDepthTestEnabled(bool enabled);
void SetDepthTestFunction(TestFunction depthFunction);
void SetStencilTestEnabled(bool enabled);
void SetStencilFunction(StencilFace face, TestFunction stencilFunction);
void SetStencilActions(StencilFace face, StencilAction stencilFailAction, StencilAction depthFailAction,
StencilAction passAction);
bool operator<(DepthStencilKey const & rhs) const;
MTLDepthStencilDescriptor * BuildDescriptor() const;
bool m_depthEnabled = false;
bool m_stencilEnabled = false;
TestFunction m_depthFunction = TestFunction::Always;
uint64_t m_stencil = 0;
};
struct PipelineKey
{
PipelineKey() = default;
PipelineKey(ref_ptr<GpuProgram> program, MTLPixelFormat colorFormat, MTLPixelFormat depthStencilFormat,
bool blendingEnabled);
bool operator<(PipelineKey const & rhs) const;
MTLRenderPipelineDescriptor * BuildDescriptor() const;
ref_ptr<GpuProgram> m_program;
MTLPixelFormat m_colorFormat = MTLPixelFormatInvalid;
MTLPixelFormat m_depthStencilFormat = MTLPixelFormatInvalid;
bool m_blendingEnabled = false;
};
struct SamplerKey
{
SamplerKey() = default;
SamplerKey(TextureFilter filter, TextureWrapping wrapSMode, TextureWrapping wrapTMode);
void Set(TextureFilter filter, TextureWrapping wrapSMode, TextureWrapping wrapTMode);
bool operator<(SamplerKey const & rhs) const;
MTLSamplerDescriptor * BuildDescriptor() const;
uint32_t m_sampler = 0;
};
id<MTLDepthStencilState> GetDepthStencilState(id<MTLDevice> device, DepthStencilKey const & key);
id<MTLRenderPipelineState> GetPipelineState(id<MTLDevice> device, PipelineKey const & key);
id<MTLSamplerState> GetSamplerState(id<MTLDevice> device, SamplerKey const & key);
void ResetPipelineStatesCache();
private:
using DepthStencilCache = std::map<DepthStencilKey, id<MTLDepthStencilState>>;
DepthStencilCache m_depthStencilCache;
using PipelineCache = std::map<PipelineKey, id<MTLRenderPipelineState>>;
PipelineCache m_pipelineCache;
using SamplerCache = std::map<SamplerKey, id<MTLSamplerState>>;
SamplerCache m_samplerCache;
};
} // namespace metal
} // namespace dp

View file

@ -0,0 +1,337 @@
#include "drape/metal/metal_states.hpp"
#include "base/assert.hpp"
#include <algorithm>
#include <string>
#include <utility>
namespace dp
{
namespace metal
{
namespace
{
// Stencil package.
uint8_t constexpr kStencilBackFunctionByte = 7;
uint8_t constexpr kStencilBackFailActionByte = 6;
uint8_t constexpr kStencilBackDepthFailActionByte = 5;
uint8_t constexpr kStencilBackPassActionByte = 4;
uint8_t constexpr kStencilFrontFunctionByte = 3;
uint8_t constexpr kStencilFrontFailActionByte = 2;
uint8_t constexpr kStencilFrontDepthFailActionByte = 1;
uint8_t constexpr kStencilFrontPassActionByte = 0;
// Sampler package.
uint8_t constexpr kWrapSModeByte = 3;
uint8_t constexpr kWrapTModeByte = 2;
uint8_t constexpr kMagFilterByte = 1;
uint8_t constexpr kMinFilterByte = 0;
template<typename T>
void SetStateByte(T & state, uint8_t value, uint8_t byteNumber)
{
auto const shift = byteNumber * 8;
auto const mask = ~(static_cast<T>(0xFF) << shift);
state = (state & mask) | (static_cast<T>(value) << shift);
}
template<typename T>
uint8_t GetStateByte(T & state, uint8_t byteNumber)
{
return static_cast<uint8_t>((state >> byteNumber * 8) & 0xFF);
}
MTLCompareFunction DecodeTestFunction(uint8_t testFunctionByte)
{
switch (static_cast<TestFunction>(testFunctionByte))
{
case TestFunction::Never: return MTLCompareFunctionNever;
case TestFunction::Less: return MTLCompareFunctionLess;
case TestFunction::Equal: return MTLCompareFunctionEqual;
case TestFunction::LessOrEqual: return MTLCompareFunctionLessEqual;
case TestFunction::Greater: return MTLCompareFunctionGreater;
case TestFunction::NotEqual: return MTLCompareFunctionNotEqual;
case TestFunction::GreaterOrEqual: return MTLCompareFunctionGreaterEqual;
case TestFunction::Always: return MTLCompareFunctionAlways;
}
ASSERT(false, ());
}
MTLStencilOperation DecodeStencilAction(uint8_t stencilActionByte)
{
switch (static_cast<StencilAction>(stencilActionByte))
{
case StencilAction::Keep: return MTLStencilOperationKeep;
case StencilAction::Zero: return MTLStencilOperationZero;
case StencilAction::Replace: return MTLStencilOperationReplace;
case StencilAction::Increment: return MTLStencilOperationIncrementClamp;
case StencilAction::IncrementWrap: return MTLStencilOperationIncrementWrap;
case StencilAction::Decrement: return MTLStencilOperationDecrementClamp;
case StencilAction::DecrementWrap: return MTLStencilOperationDecrementWrap;
case StencilAction::Invert: return MTLStencilOperationInvert;
}
ASSERT(false, ());
}
MTLSamplerMinMagFilter DecodeTextureFilter(uint8_t textureFilterByte)
{
switch (static_cast<TextureFilter>(textureFilterByte))
{
case TextureFilter::Nearest: return MTLSamplerMinMagFilterNearest;
case TextureFilter::Linear: return MTLSamplerMinMagFilterLinear;
}
ASSERT(false, ());
}
MTLSamplerAddressMode DecodeTextureWrapping(uint8_t textureWrappingByte)
{
switch (static_cast<TextureWrapping>(textureWrappingByte))
{
case TextureWrapping::ClampToEdge: return MTLSamplerAddressModeClampToEdge;
case TextureWrapping::Repeat: return MTLSamplerAddressModeRepeat;
}
ASSERT(false, ());
}
bool IsStencilFormat(MTLPixelFormat format)
{
return format == MTLPixelFormatDepth32Float_Stencil8 ||
format == MTLPixelFormatStencil8 ||
format == MTLPixelFormatX32_Stencil8;
}
} // namespace
id<MTLDepthStencilState> MetalStates::GetDepthStencilState(id<MTLDevice> device, DepthStencilKey const & key)
{
auto const it = m_depthStencilCache.find(key);
if (it != m_depthStencilCache.end())
return it->second;
id<MTLDepthStencilState> depthState = [device newDepthStencilStateWithDescriptor:key.BuildDescriptor()];
CHECK(depthState != nil, ());
m_depthStencilCache.insert(std::make_pair(key, depthState));
return depthState;
}
id<MTLRenderPipelineState> MetalStates::GetPipelineState(id<MTLDevice> device, PipelineKey const & key)
{
auto const it = m_pipelineCache.find(key);
if (it != m_pipelineCache.end())
return it->second;
NSError * error = nil;
id<MTLRenderPipelineState> pipelineState = [device newRenderPipelineStateWithDescriptor:key.BuildDescriptor()
error:&error];
if (pipelineState == nil || error != nil)
{
NSLog(@"%@", error);
CHECK(false, ("Failed to create pipeline state."));
}
m_pipelineCache.insert(std::make_pair(key, pipelineState));
return pipelineState;
}
id<MTLSamplerState> MetalStates::GetSamplerState(id<MTLDevice> device, SamplerKey const & key)
{
auto const it = m_samplerCache.find(key);
if (it != m_samplerCache.end())
return it->second;
id<MTLSamplerState> samplerState = [device newSamplerStateWithDescriptor:key.BuildDescriptor()];
CHECK(samplerState != nil, ());
m_samplerCache.insert(std::make_pair(key, samplerState));
return samplerState;
}
void MetalStates::ResetPipelineStatesCache()
{
m_pipelineCache.clear();
}
void MetalStates::DepthStencilKey::SetDepthTestEnabled(bool enabled)
{
m_depthEnabled = enabled;
}
void MetalStates::DepthStencilKey::SetDepthTestFunction(TestFunction depthFunction)
{
m_depthFunction = depthFunction;
}
void MetalStates::DepthStencilKey::SetStencilTestEnabled(bool enabled)
{
m_stencilEnabled = enabled;
}
void MetalStates::DepthStencilKey::SetStencilFunction(StencilFace face, TestFunction stencilFunction)
{
switch (face)
{
case StencilFace::Front:
SetStateByte(m_stencil, static_cast<uint8_t>(stencilFunction), kStencilFrontFunctionByte);
break;
case StencilFace::Back:
SetStateByte(m_stencil, static_cast<uint8_t>(stencilFunction), kStencilBackFunctionByte);
break;
case StencilFace::FrontAndBack:
SetStateByte(m_stencil, static_cast<uint8_t>(stencilFunction), kStencilFrontFunctionByte);
SetStateByte(m_stencil, static_cast<uint8_t>(stencilFunction), kStencilBackFunctionByte);
break;
}
}
void MetalStates::DepthStencilKey::SetStencilActions(StencilFace face, StencilAction stencilFailAction,
StencilAction depthFailAction, StencilAction passAction)
{
switch (face)
{
case StencilFace::Front:
SetStateByte(m_stencil, static_cast<uint8_t>(stencilFailAction), kStencilFrontFailActionByte);
SetStateByte(m_stencil, static_cast<uint8_t>(depthFailAction), kStencilFrontDepthFailActionByte);
SetStateByte(m_stencil, static_cast<uint8_t>(passAction), kStencilFrontPassActionByte);
break;
case StencilFace::Back:
SetStateByte(m_stencil, static_cast<uint8_t>(stencilFailAction), kStencilBackFailActionByte);
SetStateByte(m_stencil, static_cast<uint8_t>(depthFailAction), kStencilBackDepthFailActionByte);
SetStateByte(m_stencil, static_cast<uint8_t>(passAction), kStencilBackPassActionByte);
break;
case StencilFace::FrontAndBack:
SetStateByte(m_stencil, static_cast<uint8_t>(stencilFailAction), kStencilFrontFailActionByte);
SetStateByte(m_stencil, static_cast<uint8_t>(depthFailAction), kStencilFrontDepthFailActionByte);
SetStateByte(m_stencil, static_cast<uint8_t>(passAction), kStencilFrontPassActionByte);
SetStateByte(m_stencil, static_cast<uint8_t>(stencilFailAction), kStencilBackFailActionByte);
SetStateByte(m_stencil, static_cast<uint8_t>(depthFailAction), kStencilBackDepthFailActionByte);
SetStateByte(m_stencil, static_cast<uint8_t>(passAction), kStencilBackPassActionByte);
break;
}
}
bool MetalStates::DepthStencilKey::operator<(DepthStencilKey const & rhs) const
{
if (m_depthEnabled != rhs.m_depthEnabled)
return m_depthEnabled < rhs.m_depthEnabled;
if (m_stencilEnabled != rhs.m_stencilEnabled)
return m_stencilEnabled < rhs.m_stencilEnabled;
if (m_depthFunction != rhs.m_depthFunction)
return m_depthFunction < rhs.m_depthFunction;
return m_stencil < rhs.m_stencil;
}
MTLDepthStencilDescriptor * MetalStates::DepthStencilKey::BuildDescriptor() const
{
MTLDepthStencilDescriptor * desc = [[MTLDepthStencilDescriptor alloc] init];
if (m_depthEnabled)
{
desc.depthWriteEnabled = YES;
desc.depthCompareFunction = DecodeTestFunction(static_cast<uint8_t>(m_depthFunction));
}
else
{
desc.depthWriteEnabled = NO;
desc.depthCompareFunction = MTLCompareFunctionAlways;
}
if (m_stencilEnabled)
{
MTLStencilDescriptor * frontDesc = [[MTLStencilDescriptor alloc] init];
frontDesc.stencilCompareFunction = DecodeTestFunction(GetStateByte(m_stencil, kStencilFrontFunctionByte));
frontDesc.stencilFailureOperation = DecodeStencilAction(GetStateByte(m_stencil, kStencilFrontFailActionByte));
frontDesc.depthFailureOperation = DecodeStencilAction(GetStateByte(m_stencil, kStencilFrontDepthFailActionByte));
frontDesc.depthStencilPassOperation = DecodeStencilAction(GetStateByte(m_stencil, kStencilFrontPassActionByte));
desc.frontFaceStencil = frontDesc;
MTLStencilDescriptor * backDesc = [[MTLStencilDescriptor alloc] init];
backDesc.stencilCompareFunction = DecodeTestFunction(GetStateByte(m_stencil, kStencilBackFunctionByte));
backDesc.stencilFailureOperation = DecodeStencilAction(GetStateByte(m_stencil, kStencilBackFailActionByte));
backDesc.depthFailureOperation = DecodeStencilAction(GetStateByte(m_stencil, kStencilBackDepthFailActionByte));
backDesc.depthStencilPassOperation = DecodeStencilAction(GetStateByte(m_stencil, kStencilBackPassActionByte));
desc.backFaceStencil = backDesc;
}
else
{
desc.frontFaceStencil = nil;
desc.backFaceStencil = nil;
}
return desc;
}
MetalStates::PipelineKey::PipelineKey(ref_ptr<GpuProgram> program, MTLPixelFormat colorFormat,
MTLPixelFormat depthStencilFormat, bool blendingEnabled)
: m_program(std::move(program))
, m_colorFormat(colorFormat)
, m_depthStencilFormat(depthStencilFormat)
, m_blendingEnabled(blendingEnabled)
{}
bool MetalStates::PipelineKey::operator<(PipelineKey const & rhs) const
{
if (m_program != rhs.m_program)
return m_program < rhs.m_program;
if (m_colorFormat != rhs.m_colorFormat)
return m_colorFormat < rhs.m_colorFormat;
if (m_depthStencilFormat != rhs.m_depthStencilFormat)
return m_depthStencilFormat < rhs.m_depthStencilFormat;
return m_blendingEnabled < rhs.m_blendingEnabled;
}
MTLRenderPipelineDescriptor * MetalStates::PipelineKey::BuildDescriptor() const
{
MTLRenderPipelineDescriptor * desc = [[MTLRenderPipelineDescriptor alloc] init];
desc.rasterSampleCount = 1;
desc.vertexBuffers[0].mutability = MTLMutabilityImmutable; // The first VB is always immutable.
ref_ptr<MetalGpuProgram> metalProgram = m_program;
desc.vertexFunction = metalProgram->GetVertexShader();
desc.fragmentFunction = metalProgram->GetFragmentShader();
desc.vertexDescriptor = metalProgram->GetVertexDescriptor();
MTLRenderPipelineColorAttachmentDescriptor * colorAttachment = desc.colorAttachments[0];
colorAttachment.pixelFormat = m_colorFormat;
desc.depthAttachmentPixelFormat = m_depthStencilFormat;
if (IsStencilFormat(m_depthStencilFormat))
desc.stencilAttachmentPixelFormat = m_depthStencilFormat;
else
desc.stencilAttachmentPixelFormat = MTLPixelFormatInvalid;
colorAttachment.blendingEnabled = m_blendingEnabled ? YES : NO;
colorAttachment.rgbBlendOperation = MTLBlendOperationAdd;
colorAttachment.alphaBlendOperation = MTLBlendOperationAdd;
colorAttachment.sourceRGBBlendFactor = MTLBlendFactorSourceAlpha;
colorAttachment.sourceAlphaBlendFactor = MTLBlendFactorSourceAlpha;
colorAttachment.destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
colorAttachment.destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
return desc;
}
MetalStates::SamplerKey::SamplerKey(TextureFilter filter, TextureWrapping wrapSMode, TextureWrapping wrapTMode)
{
Set(filter, wrapSMode, wrapTMode);
}
void MetalStates::SamplerKey::Set(TextureFilter filter, TextureWrapping wrapSMode, TextureWrapping wrapTMode)
{
SetStateByte(m_sampler, static_cast<uint8_t>(filter), kMinFilterByte);
SetStateByte(m_sampler, static_cast<uint8_t>(filter), kMagFilterByte);
SetStateByte(m_sampler, static_cast<uint8_t>(wrapSMode), kWrapSModeByte);
SetStateByte(m_sampler, static_cast<uint8_t>(wrapTMode), kWrapTModeByte);
}
bool MetalStates::SamplerKey::operator<(SamplerKey const & rhs) const
{
return m_sampler < rhs.m_sampler;
}
MTLSamplerDescriptor * MetalStates::SamplerKey::BuildDescriptor() const
{
MTLSamplerDescriptor * desc = [[MTLSamplerDescriptor alloc] init];
desc.minFilter = DecodeTextureFilter(GetStateByte(m_sampler, kMinFilterByte));
desc.magFilter = DecodeTextureFilter(GetStateByte(m_sampler, kMagFilterByte));
desc.sAddressMode = DecodeTextureWrapping(GetStateByte(m_sampler, kWrapSModeByte));
desc.tAddressMode = DecodeTextureWrapping(GetStateByte(m_sampler, kWrapTModeByte));
return desc;
}
} // namespace metal
} // namespace dp

Some files were not shown because too many files have changed in this diff Show more