Add developer sandbox desktop app

Signed-off-by: renderexpert <expert@renderconsulting.co.uk>
This commit is contained in:
renderexpert 2025-01-10 20:07:05 +00:00 committed by Konstantin Pastbin
parent 2bdf6763ce
commit cddfc2e891
10 changed files with 1943 additions and 0 deletions

View file

@ -77,6 +77,7 @@ if (PLATFORM_DESKTOP)
set(CMAKE_OBJC_FLAGS "")
add_subdirectory(glfw)
set_target_properties(glfw PROPERTIES UNITY_BUILD OFF)
set_target_properties(glfw PROPERTIES XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC NO)
set(CMAKE_OBJC_FLAGS -fobjc-arc)
add_subdirectory(imgui)

View file

@ -90,6 +90,19 @@ else()
message(FATAL_ERROR "Unsupported platform: ${CMAKE_SYSTEM_NAME}")
endif()
if(${PLATFORM_MAC})
set(XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC YES)
# Metal language support
list(APPEND CMAKE_MODULE_PATH ${OMIM_ROOT}/3party/CMake-MetalShaderSupport/cmake)
include(CheckLanguage)
include(MetalShaderSupport)
check_language(Metal)
if(CMAKE_Metal_COMPILER)
enable_language(Metal)
endif()
endif()
# Sanitizer
if (PLATFORM_DESKTOP)
# https://clang.llvm.org/docs/UsersManual.html#controlling-code-generation
@ -384,6 +397,7 @@ if (PLATFORM_DESKTOP)
add_subdirectory(qt)
omim_add_tool_subdirectory(skin_generator)
endif()
add_subdirectory(dev_sandbox)
endif()
omim_add_test_subdirectory(qt_tstfrm)

134
dev_sandbox/CMakeLists.txt Normal file
View file

@ -0,0 +1,134 @@
project(dev_sandbox)
set(SRC
main.cpp
imgui_renderer.cpp
imgui_renderer.hpp
)
if (${PLATFORM_MAC})
append(SRC
main.mm
../iphone/Maps/Classes/MetalContextFactory.h
../iphone/Maps/Classes/MetalContextFactory.mm
)
file(GLOB_RECURSE SHADER_SOURCES_FILES ${OMIM_ROOT}/shaders/Metal/*.metal)
add_metal_shader_library(shaders_metal
${SHADER_SOURCES_FILES}
)
endif()
if (${PLATFORM_LINUX})
append(SRC
main_linux.cpp
)
endif()
omim_add_executable(${PROJECT_NAME} MACOSX_BUNDLE ${SRC})
target_link_libraries(${PROJECT_NAME}
generator # For borders::LoadBorders
map
gflags::gflags
glfw
imgui
)
# Installing Vulkan SDK is optional, however without it Vulkan dynamic libraries
# should be discovered via system paths and validation layers may not be available
find_package(Vulkan QUIET)
if (Vulkan_FOUND)
message(STATUS "Vulkan found")
target_link_libraries(${PROJECT_NAME} Vulkan::Vulkan)
endif()
if(PLATFORM_MAC)
target_embed_metal_shader_libraries(${PROJECT_NAME} shaders_metal)
endif()
target_compile_definitions(${PROJECT_NAME} PUBLIC GL_SILENCE_DEPRECATION)
if (PLATFORM_MAC)
set_target_properties(${PROJECT_NAME} PROPERTIES XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC YES)
endif()
if (PLATFORM_LINUX)
target_compile_definitions(${PROJECT_NAME} PRIVATE VK_USE_PLATFORM_XLIB_KHR)
endif()
target_include_directories(${PROJECT_NAME} PUBLIC ${OMIM_ROOT}/3party/glfw/include)
target_include_directories(${PROJECT_NAME} PUBLIC ${OMIM_ROOT}/3party/imgui)
set(BUNDLE_NAME "OMapsDevSandbox")
set(BUNDLE_DISPLAY_NAME "Organic Maps: Developer Sandbox")
set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME ${BUNDLE_NAME})
set(BUNDLE_EXECUTABLE ${BUNDLE_NAME})
set(BUNDLE_FOLDER ${CMAKE_BINARY_DIR}/${CMAKE_BUILD_TYPE}/${BUNDLE_NAME}.app)
set(RESOURCES_FOLDER ${BUNDLE_FOLDER}/Contents/Resources)
set(DATA_DIR ${OMIM_ROOT}/data)
execute_process(
COMMAND mkdir -p ${RESOURCES_FOLDER}/shaders_compiler
)
function(copy_resources)
foreach(file ${ARGN})
execute_process(
COMMAND cp -r ${DATA_DIR}/${file} ${RESOURCES_FOLDER}
)
endforeach()
endfunction()
copy_resources(
countries-strings
resources-default
resources-mdpi_light
resources-hdpi_light
resources-xhdpi_light
resources-xxhdpi_light
resources-xxxhdpi_light
resources-6plus_light
vulkan_shaders
categories.txt
categories_cuisines.txt
classificator.txt
colors.txt
countries.txt
drules_proto_default_light.bin
drules_proto_default_dark.bin
drules_proto_vehicle_light.bin
drules_proto_vehicle_dark.bin
editor.config
fonts_blacklist.txt
fonts_whitelist.txt
packed_polygons.bin
patterns.txt
transit_colors.txt
types.txt
unicode_blocks.txt
World.mwm
WorldCoasts.mwm
00_NotoNaskhArabic-Regular.ttf
00_NotoSansBengali-Regular.ttf
00_NotoSansHebrew-Regular.ttf
00_NotoSansMalayalam-Regular.ttf
00_NotoSansThai-Regular.ttf
00_NotoSerifDevanagari-Regular.ttf
01_dejavusans.ttf
02_droidsans-fallback.ttf
03_jomolhari-id-a3d.ttf
04_padauk.ttf
05_khmeros.ttf
06_code2000.ttf
07_roboto_medium.ttf
)
if (NOT PLATFORM_LINUX)
# On Linux, ICU data is loaded from the shared library.
copy_resources(icudt75l.dat)
endif()

View file

@ -0,0 +1,257 @@
#include "imgui_renderer.hpp"
#include "base/logging.hpp"
#include "base/macros.hpp"
#include <drape_frontend/render_state_extension.hpp>
#include <shaders/program_manager.hpp>
#include <imgui/imgui.h>
#include <cstring>
#include <limits>
ImguiRenderer::ImguiRenderer()
: m_state(df::CreateRenderState(gpu::Program::ImGui, df::DepthLayer::GuiLayer))
{
m_state.SetDepthTestEnabled(false);
m_state.SetBlending(dp::Blending(true));
}
void ImguiRenderer::Render(ref_ptr<dp::GraphicsContext> context, ref_ptr<dp::TextureManager> textureManager,
ref_ptr<gpu::ProgramManager> programManager)
{
std::lock_guard<std::mutex> lock(m_bufferMutex);
size_t renderDataIndex = (m_updateIndex + 1) % m_uiDataBuffer.size();
UiDataBuffer & dataBuffer = m_uiDataBuffer[renderDataIndex];
auto gpuProgram = programManager->GetProgram(m_state.GetProgram<gpu::Program>());
bool needUpdate = true;
if (!m_mesh || dataBuffer.m_vertices.size() > m_vertexCount || dataBuffer.m_indices.size() > m_indexCount)
{
while (dataBuffer.m_vertices.size() > m_vertexCount)
m_vertexCount *= 2;
while (dataBuffer.m_indices.size() > m_indexCount)
m_indexCount *= 2;
m_indexCount = std::min(m_indexCount, static_cast<uint32_t>(std::numeric_limits<uint16_t>::max()));
dataBuffer.m_vertices.resize(m_vertexCount);
dataBuffer.m_indices.resize(m_indexCount);
m_mesh = make_unique_dp<dp::MeshObject>(context, dp::MeshObject::DrawPrimitive::Triangles, "imGui");
m_mesh->SetBuffer(0, std::move(dataBuffer.m_vertices));
m_mesh->SetAttribute("a_position", 0, 0 /* offset */, 2);
m_mesh->SetAttribute("a_texCoords", 0, 2 * sizeof(float) /* offset */, 2);
m_mesh->SetAttribute("a_color", 0, 4 * sizeof(float) /* offset */, 4);
m_mesh->SetIndexBuffer(std::move(dataBuffer.m_indices));
m_mesh->Build(context, gpuProgram);
dataBuffer.m_vertices.clear();
dataBuffer.m_indices.clear();
needUpdate = false;
}
if (!m_texture)
{
std::lock_guard<std::mutex> lock(m_textureMutex);
if (!m_textureData.empty())
{
m_texture = make_unique_dp<dp::StaticTexture>();
m_texture->Create(context,
dp::Texture::Params{
.m_width = m_textureWidth,
.m_height = m_textureHeight,
.m_format = dp::TextureFormat::RGBA8,
.m_allocator = textureManager->GetTextureAllocator(),
},
m_textureData.data());
m_textureData.clear();
m_state.SetColorTexture(make_ref(m_texture));
}
else
{
// Can't render without texture.
return;
}
}
if (dataBuffer.m_drawCalls.empty())
return;
if (needUpdate && !dataBuffer.m_vertices.empty() && !dataBuffer.m_indices.empty())
{
m_mesh->UpdateBuffer(context, 0, dataBuffer.m_vertices);
m_mesh->UpdateIndexBuffer(context, dataBuffer.m_indices);
dataBuffer.m_vertices.clear();
dataBuffer.m_indices.clear();
}
gpu::ImGuiProgramParams const params{.m_projection = m_projection};
context->PushDebugLabel("ImGui Rendering");
m_mesh->Render(context, gpuProgram, m_state, programManager->GetParamsSetter(), params,
[&, this]()
{
context->SetCullingEnabled(false);
for (auto const & drawCall : dataBuffer.m_drawCalls)
{
uint32_t y = drawCall.clipRect.y;
if (context->GetApiVersion() == dp::ApiVersion::OpenGLES3)
y = dataBuffer.m_height - y - drawCall.clipRect.w;
context->SetScissor(drawCall.clipRect.x, y, drawCall.clipRect.z, drawCall.clipRect.w);
m_mesh->DrawPrimitivesSubsetIndexed(context, drawCall.indexCount, drawCall.startIndex);
}
context->SetCullingEnabled(true);
context->SetScissor(0, 0, dataBuffer.m_width, dataBuffer.m_height);
});
context->PopDebugLabel();
}
void ImguiRenderer::Update(std::function<void()> const & uiCallback)
{
CHECK(uiCallback, ());
ImGuiIO & io = ImGui::GetIO();
if (!io.Fonts->IsBuilt())
io.Fonts->Build();
if (!m_texture)
UpdateTexture();
ImGui::NewFrame();
uiCallback();
ImGui::Render();
UpdateBuffers();
}
void ImguiRenderer::Reset()
{
{
std::lock_guard<std::mutex> lock(m_textureMutex);
m_texture.reset();
}
{
std::lock_guard<std::mutex> lock(m_bufferMutex);
m_mesh.reset();
}
}
void ImguiRenderer::UpdateTexture()
{
std::lock_guard<std::mutex> lock(m_textureMutex);
unsigned char * pixels;
int width, height;
ImGui::GetIO().Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
auto const sizeInBytes = width * height * sizeof(uint32_t);
m_textureData.resize(sizeInBytes);
memcpy(m_textureData.data(), pixels, sizeInBytes);
m_textureWidth = static_cast<uint32_t>(width);
m_textureHeight = static_cast<uint32_t>(height);
}
void ImguiRenderer::UpdateBuffers()
{
UiDataBuffer & dataBuffer = m_uiDataBuffer[m_updateIndex];
dataBuffer.m_drawCalls.clear();
ImDrawData * dd = ImGui::GetDrawData();
auto const fbWidth = static_cast<int>(dd->DisplaySize.x * dd->FramebufferScale.x);
auto const fbHeight = static_cast<int>(dd->DisplaySize.y * dd->FramebufferScale.y);
if (fbWidth <= 0 || fbHeight <= 0 || dd->CmdListsCount == 0 || dd->TotalIdxCount == 0 || dd->TotalVtxCount == 0)
return;
dataBuffer.m_width = static_cast<uint32_t>(fbWidth);
dataBuffer.m_height = static_cast<uint32_t>(fbHeight);
CHECK(dd->TotalVtxCount <= std::numeric_limits<uint16_t>::max(),
("UI is so complex and now requires 32-bit indices. You need to improve dp::MeshObject or simplify UI"));
CHECK((ImGui::GetIO().BackendFlags & ImGuiBackendFlags_RendererHasVtxOffset) == 0, ());
dataBuffer.m_vertices.resize(dd->TotalVtxCount);
dataBuffer.m_indices.resize(dd->TotalIdxCount);
int totalDrawCallsCount = 0;
for (int i = 0; i < dd->CmdListsCount; ++i)
totalDrawCallsCount += dd->CmdLists[i]->CmdBuffer.Size;
dataBuffer.m_drawCalls.reserve(totalDrawCallsCount);
ImVec2 const clipOff = dd->DisplayPos;
ImVec2 const clipScale = dd->FramebufferScale;
uint32_t vertexOffset = 0;
uint32_t indexOffset = 0;
for (int i = 0; i < dd->CmdListsCount; ++i)
{
ImDrawList const * cmdList = dd->CmdLists[i];
for (int j = 0; j < cmdList->VtxBuffer.Size; ++j)
{
dp::Color color(cmdList->VtxBuffer.Data[j].col);
dataBuffer.m_vertices[j + vertexOffset] = {
.position = {cmdList->VtxBuffer.Data[j].pos.x, cmdList->VtxBuffer.Data[j].pos.y},
.texCoords = {cmdList->VtxBuffer.Data[j].uv.x, cmdList->VtxBuffer.Data[j].uv.y},
.color = {color.GetAlphaF(), color.GetBlueF(), color.GetGreenF(),
color.GetRedF()} // Byte order is reversed in imGui
};
}
static_assert(sizeof(uint16_t) == sizeof(ImDrawIdx));
memcpy(dataBuffer.m_indices.data() + indexOffset, cmdList->IdxBuffer.Data,
cmdList->IdxBuffer.Size * sizeof(ImDrawIdx));
for (int j = 0; j < cmdList->IdxBuffer.Size; ++j)
{
uint32_t indexValue = dataBuffer.m_indices[j + indexOffset];
indexValue += vertexOffset;
CHECK(indexValue <= std::numeric_limits<uint16_t>::max(), ());
dataBuffer.m_indices[j + indexOffset] = static_cast<uint16_t>(indexValue);
}
for (int cmdIndex = 0; cmdIndex < cmdList->CmdBuffer.Size; ++cmdIndex)
{
const ImDrawCmd cmd = cmdList->CmdBuffer[cmdIndex];
ImVec2 clipMin((cmd.ClipRect.x - clipOff.x) * clipScale.x, (cmd.ClipRect.y - clipOff.y) * clipScale.y);
ImVec2 clipMax((cmd.ClipRect.z - clipOff.x) * clipScale.x, (cmd.ClipRect.w - clipOff.y) * clipScale.y);
if (clipMin.x < 0.0f)
clipMin.x = 0.0f;
if (clipMin.y < 0.0f)
clipMin.y = 0.0f;
if (clipMax.x > fbWidth)
clipMax.x = static_cast<float>(fbWidth);
if (clipMax.y > fbHeight)
clipMax.y = static_cast<float>(fbHeight);
if (clipMax.x <= clipMin.x || clipMax.y <= clipMin.y)
continue;
dataBuffer.m_drawCalls.emplace_back(DrawCall{
.indexCount = static_cast<uint32_t>(cmd.ElemCount),
.startIndex = static_cast<uint32_t>(indexOffset + cmd.IdxOffset),
.clipRect = {static_cast<uint32_t>(clipMin.x), static_cast<uint32_t>(clipMin.y),
static_cast<uint32_t>(clipMax.x - clipMin.x), static_cast<uint32_t>(clipMax.y - clipMin.y)}});
}
vertexOffset += static_cast<uint32_t>(cmdList->VtxBuffer.Size);
indexOffset += static_cast<uint32_t>(cmdList->IdxBuffer.Size);
}
CHECK(vertexOffset == dataBuffer.m_vertices.size(), ());
CHECK(indexOffset == dataBuffer.m_indices.size(), ());
{
std::lock_guard<std::mutex> lock(m_bufferMutex);
// Projection
float const left = dd->DisplayPos.x;
float const right = dd->DisplayPos.x + dd->DisplaySize.x;
float const top = dd->DisplayPos.y;
float const bottom = dd->DisplayPos.y + dd->DisplaySize.y;
m_projection[0][0] = 2.0f / (right - left);
m_projection[1][1] = 2.0f / (top - bottom);
m_projection[2][2] = -1.0f;
m_projection[3][3] = 1.0f;
m_projection[0][3] = -(right + left) / (right - left);
m_projection[1][3] = -(top + bottom) / (top - bottom);
// Swap buffers
m_updateIndex = (m_updateIndex + 1) % m_uiDataBuffer.size();
}
}

View file

@ -0,0 +1,72 @@
#pragma once
#include "drape/glsl_types.hpp"
#include "drape/graphics_context.hpp"
#include "drape/mesh_object.hpp"
#include "drape/pointers.hpp"
#include "drape/render_state.hpp"
#include "drape/static_texture.hpp"
#include "drape/texture_manager.hpp"
#include <array>
#include <cstdint>
#include <functional>
#include <mutex>
#include <vector>
class ImguiRenderer
{
public:
ImguiRenderer();
void Render(ref_ptr<dp::GraphicsContext> context,
ref_ptr<dp::TextureManager> textureManager,
ref_ptr<gpu::ProgramManager> programManager);
void Update(std::function<void()> const & uiCallback);
void Reset();
private:
void UpdateTexture();
void UpdateBuffers();
struct ImguiVertex
{
glsl::vec2 position;
glsl::vec2 texCoords;
glsl::vec4 color;
};
static_assert(sizeof(ImguiVertex) == 2 * sizeof(glsl::vec4));
struct DrawCall
{
uint32_t indexCount = 0;
uint32_t startIndex = 0;
glsl::uvec4 clipRect{};
};
drape_ptr<dp::MeshObject> m_mesh;
uint32_t m_vertexCount = 2000;
uint32_t m_indexCount = 3000;
drape_ptr<dp::StaticTexture> m_texture;
std::vector<unsigned char> m_textureData;
uint32_t m_textureWidth = 0;
uint32_t m_textureHeight = 0;
dp::RenderState m_state;
struct UiDataBuffer
{
std::vector<ImguiVertex> m_vertices;
std::vector<uint16_t> m_indices;
std::vector<DrawCall> m_drawCalls;
uint32_t m_width;
uint32_t m_height;
};
std::array<UiDataBuffer, 2> m_uiDataBuffer;
size_t m_updateIndex = 0;
glsl::mat4 m_projection;
std::mutex m_bufferMutex;
std::mutex m_textureMutex;
};

682
dev_sandbox/main.cpp Normal file
View file

@ -0,0 +1,682 @@
#include "dev_sandbox/imgui_renderer.hpp"
#include "map/framework.hpp"
#include "platform/platform.hpp"
#include "platform/settings.hpp"
#include "coding/reader.hpp"
#include "base/logging.hpp"
#include "base/macros.hpp"
#include "std/target_os.hpp"
#include <chrono>
#include <functional>
#include <mutex>
#include <optional>
#include <sstream>
#include <string>
#include <string_view>
#include <vector>
#include <gflags/gflags.h>
#if defined(OMIM_OS_WINDOWS)
#define GLFW_EXPOSE_NATIVE_WIN32
#elif defined(OMIM_OS_LINUX)
#define GLFW_EXPOSE_NATIVE_X11
#elif defined(OMIM_OS_MAC)
#define GLFW_EXPOSE_NATIVE_COCOA
#else
#error Unsupported plaform
#endif
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
#include <GLFW/glfw3native.h>
#include <imgui/backends/imgui_impl_glfw.h>
#include <imgui/imgui.h>
DEFINE_string(data_path, "", "Path to data directory.");
DEFINE_string(log_abort_level, base::ToString(base::GetDefaultLogAbortLevel()),
"Log messages severity that causes termination.");
DEFINE_string(resources_path, "", "Path to resources directory.");
DEFINE_string(lang, "", "Device language.");
#if defined(OMIM_OS_MAC) || defined(OMIM_OS_LINUX)
drape_ptr<dp::GraphicsContextFactory> CreateContextFactory(GLFWwindow * window, dp::ApiVersion api, m2::PointU size);
void PrepareDestroyContextFactory(ref_ptr<dp::GraphicsContextFactory> contextFactory);
void OnCreateDrapeEngine(GLFWwindow * window, dp::ApiVersion api, ref_ptr<dp::GraphicsContextFactory> contextFactory);
void UpdateContentScale(GLFWwindow * window, float scale);
void UpdateSize(ref_ptr<dp::GraphicsContextFactory> contextFactory, int w, int h);
#endif
#if defined(OMIM_OS_LINUX)
// Workaround for storage::Status compilation issue:
// /usr/include/X11/Xlib.h:83:16: note: expanded from macro 'Status'
#undef Status
#endif
namespace
{
bool ValidateLogAbortLevel(char const * flagname, std::string const & value)
{
if (auto level = base::FromString(value); !level)
{
std::cerr << "Invalid value for --" << flagname << ": " << value << ", must be one of: ";
auto const & names = base::GetLogLevelNames();
for (size_t i = 0; i < names.size(); ++i)
{
if (i != 0)
std::cerr << ", ";
std::cerr << names[i];
}
std::cerr << '\n';
return false;
}
return true;
}
bool const g_logAbortLevelDummy = gflags::RegisterFlagValidator(&FLAGS_log_abort_level, &ValidateLogAbortLevel);
void errorCallback(int error, char const * description) { LOG(LERROR, ("GLFW (", error, "):", description)); }
struct WindowHandlers
{
std::function<void(int w, int h)> onResize;
std::function<void(double x, double y, int button, int action, int mods)> onMouseButton;
std::function<void(double x, double y)> onMouseMove;
std::function<void(double x, double y, double xOffset, double yOffset)> onScroll;
std::function<void(int key, int scancode, int action, int mods)> onKeyboardButton;
std::function<void(float xscale, float yscale)> onContentScale;
} handlers;
df::Touch GetTouch(double x, double y)
{
return df::Touch{.m_location = m2::PointF(static_cast<float>(x), static_cast<float>(y)), .m_id = 0};
}
df::Touch GetSymmetricalTouch(Framework & framework, df::Touch const & touch)
{
m2::PointD const pixelCenter = framework.GetVisiblePixelCenter();
m2::PointD const symmetricalLocation = pixelCenter + pixelCenter - m2::PointD(touch.m_location);
df::Touch result;
result.m_id = touch.m_id + 1;
result.m_location = symmetricalLocation;
return result;
}
df::TouchEvent GetTouchEvent(Framework & framework, double x, double y, int mods, df::TouchEvent::ETouchType type)
{
df::TouchEvent event;
event.SetTouchType(type);
event.SetFirstTouch(GetTouch(x, y));
if (mods & GLFW_MOD_SUPER)
event.SetSecondTouch(GetSymmetricalTouch(framework, event.GetFirstTouch()));
return event;
}
void FormatMapSize(uint64_t sizeInBytes, std::string & units, size_t & sizeToDownload)
{
int const mbInBytes = 1024 * 1024;
int const kbInBytes = 1024;
if (sizeInBytes > mbInBytes)
{
sizeToDownload = (sizeInBytes + mbInBytes - 1) / mbInBytes;
units = "MB";
}
else if (sizeInBytes > kbInBytes)
{
sizeToDownload = (sizeInBytes + kbInBytes - 1) / kbInBytes;
units = "KB";
}
else
{
sizeToDownload = sizeInBytes;
units = "B";
}
}
std::string_view GetMyPoisitionText(location::EMyPositionMode mode)
{
switch(mode)
{
case location::EMyPositionMode::PendingPosition: return "Pending";
case location::EMyPositionMode::NotFollowNoPosition: return "No position";
case location::EMyPositionMode::NotFollow: return "Not follow";
case location::EMyPositionMode::Follow: return "Follow";
case location::EMyPositionMode::FollowAndRotate: return "Follow and Rotate";
}
return "";
}
dp::ApiVersion GetApiVersion(char const * apiLabel)
{
std::string_view v(apiLabel);
if (v == "Metal") return dp::ApiVersion::Metal;
if (v == "Vulkan") return dp::ApiVersion::Vulkan;
if (v == "OpenGL") return dp::ApiVersion::OpenGLES3;
return dp::ApiVersion::Invalid;
}
#if defined(OMIM_OS_LINUX)
class LinuxGuiThread : public base::TaskLoop
{
public:
PushResult Push(Task && task) override
{
std::lock_guard<std::mutex> lock(m_mutex);
m_tasks.emplace_back(std::move(task));
return {true, base::TaskLoop::kNoId};
}
PushResult Push(Task const & task) override
{
std::lock_guard<std::mutex> lock(m_mutex);
m_tasks.emplace_back(task);
return {true, base::TaskLoop::kNoId};
}
void ExecuteTasks()
{
std::lock_guard<std::mutex> lock(m_mutex);
for (auto & task : m_tasks)
task();
m_tasks.clear();
}
private:
std::vector<Task> m_tasks;
std::mutex m_mutex;
};
#endif
} // namespace
int main(int argc, char * argv[])
{
// Our double parsing code (base/string_utils.hpp) needs dots as a floating point delimiters, not commas.
// TODO: Refactor our doubles parsing code to use locale-independent delimiters.
// For example, https://github.com/google/double-conversion can be used.
// See http://dbaron.org/log/20121222-locale for more details.
(void)::setenv("LC_NUMERIC", "C", 1);
Platform & platform = GetPlatform();
LOG(LINFO, ("Organic Maps: Developer Sandbox", platform.Version(), "detected CPU cores:", platform.CpuCores()));
gflags::SetUsageMessage("Developer Sandbox.");
gflags::SetVersionString(platform.Version());
gflags::ParseCommandLineFlags(&argc, &argv, true);
if (!FLAGS_resources_path.empty())
platform.SetResourceDir(FLAGS_resources_path);
if (!FLAGS_data_path.empty())
platform.SetWritableDirForTests(FLAGS_data_path);
if (auto const logLevel = base::FromString(FLAGS_log_abort_level); logLevel)
base::g_LogAbortLevel = *logLevel;
else
LOG(LCRITICAL, ("Invalid log level:", FLAGS_log_abort_level));
#if defined(OMIM_OS_LINUX)
auto guiThread = std::make_unique<LinuxGuiThread>();
auto guiThreadPtr = guiThread.get();
platform.SetGuiThread(std::move(guiThread));
#endif
// Init GLFW.
glfwSetErrorCallback(errorCallback);
if (!glfwInit())
{
return -1;
}
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
#if defined(OMIM_OS_WINDOWS)
glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE);
#endif
auto monitor = glfwGetPrimaryMonitor();
auto mode = glfwGetVideoMode(monitor);
GLFWwindow * window =
glfwCreateWindow(mode->width, mode->height, "Organic Maps: Developer Sandbox", nullptr, nullptr);
int fbWidth = 0, fbHeight = 0;
glfwGetFramebufferSize(window, &fbWidth, &fbHeight);
float xs = 1.0f, ys = 1.0f;
glfwGetWindowContentScale(window, &xs, &ys);
float visualScale = std::max(xs, ys);
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
ImGui::StyleColorsClassic();
glfwMaximizeWindow(window);
platform.SetupMeasurementSystem();
bool outvalue;
if (!settings::Get(settings::kDeveloperMode, outvalue))
settings::Set(settings::kDeveloperMode, true);
if (!FLAGS_lang.empty())
(void)::setenv("LANGUAGE", FLAGS_lang.c_str(), 1);
FrameworkParams frameworkParams;
Framework framework(frameworkParams);
ImguiRenderer imguiRenderer;
Framework::DrapeCreationParams drapeParams{
#if defined(OMIM_OS_MAC)
.m_apiVersion = dp::ApiVersion::Metal,
#else
.m_apiVersion = dp::ApiVersion::Vulkan,
#endif
.m_visualScale = visualScale,
.m_surfaceWidth = fbWidth,
.m_surfaceHeight = fbHeight,
.m_renderInjectionHandler = [&](ref_ptr<dp::GraphicsContext> context,
ref_ptr<dp::TextureManager> textureManager,
ref_ptr<gpu::ProgramManager> programManager,
bool shutdown)
{
if (shutdown)
imguiRenderer.Reset();
else
imguiRenderer.Render(context, textureManager, programManager);
}};
gui::Skin guiSkin(gui::ResolveGuiSkinFile("default"), visualScale);
guiSkin.Resize(fbWidth, fbHeight);
guiSkin.ForEach([&](gui::EWidget widget, gui::Position const & pos) { drapeParams.m_widgetsInitInfo[widget] = pos; });
drapeParams.m_widgetsInitInfo[gui::WIDGET_SCALE_FPS_LABEL] = gui::Position(dp::LeftTop);
drape_ptr<dp::GraphicsContextFactory> contextFactory;
auto CreateDrapeEngine = [&](dp::ApiVersion version)
{
drapeParams.m_apiVersion = version;
drapeParams.m_visualScale = visualScale;
drapeParams.m_surfaceWidth = fbWidth;
drapeParams.m_surfaceHeight = fbHeight;
contextFactory = CreateContextFactory(window, drapeParams.m_apiVersion,
m2::PointU(static_cast<uint32_t>(drapeParams.m_surfaceWidth),
static_cast<uint32_t>(drapeParams.m_surfaceHeight)));
auto params = drapeParams;
framework.CreateDrapeEngine(make_ref(contextFactory), std::move(params));
OnCreateDrapeEngine(window, version, make_ref(contextFactory));
framework.SetRenderingEnabled(nullptr);
};
CreateDrapeEngine(drapeParams.m_apiVersion);
auto DestroyDrapeEngine = [&]()
{
framework.SetRenderingDisabled(true);
framework.DestroyDrapeEngine();
PrepareDestroyContextFactory(make_ref(contextFactory));
contextFactory.reset();
};
// Process resizing.
handlers.onResize = [&](int w, int h)
{
fbWidth = w;
fbHeight = h;
if (fbWidth > 0 && fbHeight > 0)
{
UpdateSize(make_ref(contextFactory), fbWidth, fbHeight);
framework.OnSize(fbWidth, fbHeight);
guiSkin.Resize(w, h);
gui::TWidgetsLayoutInfo layout;
guiSkin.ForEach([&layout](gui::EWidget w, gui::Position const & pos) { layout[w] = pos.m_pixelPivot; });
framework.SetWidgetLayout(std::move(layout));
framework.MakeFrameActive();
}
};
glfwSetFramebufferSizeCallback(window, [](GLFWwindow * wnd, int w, int h) { handlers.onResize(w, h); });
// Process change content scale.
handlers.onContentScale = [&](float xscale, float yscale)
{
visualScale = std::max(xscale, yscale);
framework.UpdateVisualScale(visualScale);
int w = 0, h = 0;
glfwGetWindowSize(window, &w, &h);
#if defined(OMIM_OS_MAC)
w *= xscale;
h *= yscale;
#endif
if (w != fbWidth || h != fbHeight)
{
#if defined(OMIM_OS_MAC)
UpdateContentScale(window, xscale);
#endif
fbWidth = w;
fbHeight = h;
UpdateSize(make_ref(contextFactory), fbWidth, fbHeight);
framework.OnSize(fbWidth, fbHeight);
}
};
glfwSetWindowContentScaleCallback(window, [](GLFWwindow *, float xscale, float yscale)
{
handlers.onContentScale(xscale, yscale);
});
// Location handler
std::optional<ms::LatLon> lastLatLon;
bool bearingEnabled = false;
float bearing = 0.0f;
auto setUserLocation = [&]()
{
if (lastLatLon)
{
framework.OnLocationUpdate(
location::GpsInfo{.m_source = location::EUser,
.m_timestamp = static_cast<double>(std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch())
.count()) /
1000,
.m_latitude = lastLatLon->m_lat,
.m_longitude = lastLatLon->m_lon,
.m_horizontalAccuracy = 10,
.m_bearing = bearingEnabled ? bearing : -1.0f});
if (bearingEnabled)
{
framework.OnCompassUpdate(location::CompassInfo{.m_bearing = base::DegToRad(bearing)});
}
}
};
// Download maps handler
std::string downloadButtonLabel;
std::string retryButtonLabel;
std::string downloadStatusLabel;
storage::CountryId lastCountry;
auto const onCountryChanged = [&](storage::CountryId const & countryId)
{
downloadButtonLabel.clear();
retryButtonLabel.clear();
downloadStatusLabel.clear();
lastCountry = countryId;
// Called by Framework in World zoom level.
if (countryId.empty())
return;
auto const & storage = framework.GetStorage();
auto status = storage.CountryStatusEx(countryId);
auto const & countryName = countryId;
if (status == storage::Status::NotDownloaded)
{
std::string units;
size_t sizeToDownload = 0;
FormatMapSize(storage.CountrySizeInBytes(countryId).second, units, sizeToDownload);
std::stringstream str;
str << "Download (" << countryName << ") " << sizeToDownload << units;
downloadButtonLabel = str.str();
}
else if (status == storage::Status::InQueue)
{
std::stringstream str;
str << countryName << " is waiting for downloading";
downloadStatusLabel = str.str();
}
else if (status != storage::Status::Downloading && status != storage::Status::OnDisk &&
status != storage::Status::OnDiskOutOfDate)
{
std::stringstream str;
str << "Retry to download " << countryName;
retryButtonLabel = str.str();
}
};
framework.SetCurrentCountryChangedListener(onCountryChanged);
framework.GetStorage().Subscribe(
[&](storage::CountryId const & countryId)
{
// Storage also calls notifications for parents, but we are interested in leafs only.
if (framework.GetStorage().IsLeaf(countryId))
onCountryChanged(countryId);
},
[&](storage::CountryId const & countryId, downloader::Progress const & progress)
{
std::stringstream str;
str << "Downloading (" << countryId << ") " << (progress.m_bytesDownloaded * 100 / progress.m_bytesTotal)
<< "%";
downloadStatusLabel = str.str();
framework.MakeFrameActive();
});
// Handle mouse buttons.
bool touchActive = false;
int touchMods = 0;
bool setUpLocationByLeftClick = false;
handlers.onMouseButton = [&](double x, double y, int button, int action, int mods)
{
#if defined(OMIM_OS_LINUX)
ImGui::GetIO().MousePos = ImVec2(x / visualScale, y / visualScale);
#endif
if (ImGui::GetIO().WantCaptureMouse)
{
framework.MakeFrameActive();
return;
}
#if defined(OMIM_OS_MAC)
x *= visualScale;
y *= visualScale;
#endif
lastLatLon = mercator::ToLatLon(framework.PtoG(m2::PointD(x, y)));
if (setUpLocationByLeftClick)
{
setUserLocation();
return;
}
if (button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_PRESS)
{
framework.TouchEvent(GetTouchEvent(framework, x, y, mods, df::TouchEvent::TOUCH_DOWN));
touchActive = true;
touchMods = mods;
}
if (touchActive && action == GLFW_RELEASE)
{
framework.TouchEvent(GetTouchEvent(framework, x, y, 0, df::TouchEvent::TOUCH_UP));
touchActive = false;
touchMods = 0;
}
};
glfwSetMouseButtonCallback(window,
[](GLFWwindow * wnd, int button, int action, int mods)
{
double x, y;
glfwGetCursorPos(wnd, &x, &y);
handlers.onMouseButton(x, y, button, action, mods);
});
// Handle mouse moving.
handlers.onMouseMove = [&](double x, double y)
{
#if defined(OMIM_OS_LINUX)
ImGui::GetIO().MousePos = ImVec2(x / visualScale, y / visualScale);
#endif
if (ImGui::GetIO().WantCaptureMouse)
framework.MakeFrameActive();
if (touchActive)
{
#if defined(OMIM_OS_MAC)
x *= visualScale;
y *= visualScale;
#endif
framework.TouchEvent(
GetTouchEvent(framework, x, y, touchMods, df::TouchEvent::TOUCH_MOVE));
}
};
glfwSetCursorPosCallback(window, [](GLFWwindow *, double x, double y)
{
handlers.onMouseMove(x, y);
});
// Handle scroll.
handlers.onScroll = [&](double x, double y, double xOffset, double yOffset)
{
#if defined(OMIM_OS_LINUX)
ImGui::GetIO().MousePos = ImVec2(x / visualScale, y / visualScale);
#endif
if (ImGui::GetIO().WantCaptureMouse)
{
framework.MakeFrameActive();
return;
}
#if defined(OMIM_OS_MAC)
x *= visualScale;
y *= visualScale;
#endif
constexpr double kSensitivity = 0.01;
double const factor = yOffset * kSensitivity;
framework.Scale(exp(factor), m2::PointD(x, y), false);
};
glfwSetScrollCallback(window,
[](GLFWwindow * wnd, double xoffset, double yoffset)
{
double x, y;
glfwGetCursorPos(wnd, &x, &y);
handlers.onScroll(x, y, xoffset, yoffset);
});
// Keys.
handlers.onKeyboardButton = [&](int key, int scancode, int action, int mods) {};
glfwSetKeyCallback(window, [](GLFWwindow *, int key, int scancode, int action, int mods)
{
handlers.onKeyboardButton(key, scancode, action, mods);
});
// imGui UI
bool enableDebugRectRendering = false;
bool enableAA = false;
auto imGuiUI = [&]()
{
ImGui::SetNextWindowPos(ImVec2(5, 20), ImGuiCond_Appearing);
ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
// Drape controls
char const * apiLabels[] = {
#if defined(OMIM_OS_MAC)
"Metal",
"Vulkan",
"OpenGL"
#elif defined(OMIM_OS_LINUX)
"Vulkan",
"OpenGL"
#endif
};
static int currentAPI = 0;
if (ImGui::Combo("API", &currentAPI, apiLabels, IM_ARRAYSIZE(apiLabels)))
{
auto const apiVersion = GetApiVersion(apiLabels[currentAPI]);
if (framework.GetDrapeEngine()->GetApiVersion() != apiVersion)
{
DestroyDrapeEngine();
CreateDrapeEngine(apiVersion);
}
}
if (ImGui::Checkbox("Debug rect rendering", &enableDebugRectRendering))
framework.EnableDebugRectRendering(enableDebugRectRendering);
if (ImGui::Checkbox("Antialiasing", &enableAA))
framework.GetDrapeEngine()->SetPosteffectEnabled(df::PostprocessRenderer::Antialiasing, enableAA);
ImGui::NewLine();
ImGui::Separator();
ImGui::NewLine();
// Map controls
if (ImGui::Button("Scale +"))
framework.Scale(Framework::SCALE_MAG, true);
ImGui::SameLine();
if (ImGui::Button("Scale -"))
framework.Scale(Framework::SCALE_MIN, true);
ImGui::Checkbox("Set up location by left click", &setUpLocationByLeftClick);
if (setUpLocationByLeftClick)
{
if (ImGui::Checkbox("Bearing", &bearingEnabled))
setUserLocation();
ImGui::SameLine();
if (ImGui::SliderFloat(" ", &bearing, 0.0f, 360.0f, "%.1f"))
setUserLocation();
}
ImGui::Text("My positon mode: %s", GetMyPoisitionText(framework.GetMyPositionMode()).data());
if (ImGui::Button("Next Position Mode"))
framework.SwitchMyPositionNextMode();
ImGui::NewLine();
ImGui::Separator();
ImGui::NewLine();
// No downloading on Linux at the moment, need to implement http_thread without Qt.
#if !defined(OMIM_OS_LINUX)
// Download controls
if (!downloadButtonLabel.empty())
{
if (ImGui::Button(downloadButtonLabel.c_str()))
framework.GetStorage().DownloadNode(lastCountry);
}
if (!retryButtonLabel.empty())
{
if (ImGui::Button(retryButtonLabel.c_str()))
framework.GetStorage().RetryDownloadNode(lastCountry);
}
if (!downloadStatusLabel.empty())
ImGui::Text("%s", downloadStatusLabel.c_str());
if (!downloadButtonLabel.empty() || !retryButtonLabel.empty() || !downloadStatusLabel.empty())
{
ImGui::NewLine();
ImGui::Separator();
ImGui::NewLine();
}
#endif
ImGui::End();
};
ImGui_ImplGlfw_InitForOther(window, true);
// Main loop.
while (!glfwWindowShouldClose(window))
{
glfwPollEvents();
#if defined(OMIM_OS_LINUX)
guiThreadPtr->ExecuteTasks();
#endif
// Render imGui UI
ImGui_ImplGlfw_NewFrame();
ImGuiIO& io = ImGui::GetIO();
#if defined(OMIM_OS_LINUX)
// Apply correct visual scale on Linux
// In glfw for Linux, window size and framebuffer size are the same,
// even if visual scale is not 1.0. It's different from behaviour on Mac.
io.DisplaySize = ImVec2(fbWidth / visualScale, fbHeight / visualScale);
io.DisplayFramebufferScale = ImVec2(visualScale, visualScale);
double mouseX, mouseY;
glfwGetCursorPos(window, &mouseX, &mouseY);
io.AddMousePosEvent((float)mouseX / visualScale, (float)mouseY / visualScale);
#endif
io.IniFilename = nullptr;
imguiRenderer.Update(imGuiUI);
std::this_thread::sleep_for(std::chrono::milliseconds(1000 / 30));
}
framework.EnterBackground();
DestroyDrapeEngine();
ImGui_ImplGlfw_Shutdown();
ImGui::DestroyContext();
glfwDestroyWindow(window);
glfwTerminate();
return 0;
}

370
dev_sandbox/main.mm Normal file
View file

@ -0,0 +1,370 @@
#include "iphone/Maps/Classes/MetalContextFactory.h"
#include "drape/gl_functions.hpp"
#include "drape/oglcontext.hpp"
#include "drape/metal/metal_base_context.hpp"
#include "drape/vulkan/vulkan_context_factory.hpp"
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
#if __APPLE__
#define GLFW_EXPOSE_NATIVE_COCOA
#else
#error Unsupported OS
#endif
#include <GLFW/glfw3native.h>
#import <AppKit/NSOpenGL.h>
#import <Cocoa/Cocoa.h>
#import <Metal/Metal.h>
#import <QuartzCore/CAMetalLayer.h>
#include <vulkan/vulkan_macos.h>
#include <atomic>
#include <memory>
class MacOSVulkanContextFactory : public dp::vulkan::VulkanContextFactory
{
public:
MacOSVulkanContextFactory()
: dp::vulkan::VulkanContextFactory(1, 33, false) {}
void SetSurface(CAMetalLayer *layer)
{
VkMacOSSurfaceCreateInfoMVK createInfo = {
.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK,
.flags = 0,
.pView = static_cast<const void *>(CFBridgingRetain(layer)),
};
VkResult statusCode;
CHECK(vkCreateMacOSSurfaceMVK, ());
statusCode = vkCreateMacOSSurfaceMVK(m_vulkanInstance, &createInfo, nullptr,
&m_surface);
if (statusCode != VK_SUCCESS)
{
LOG_ERROR_VK_CALL(vkCreateMacOSSurfaceMVK, statusCode);
return;
}
uint32_t const renderingQueueIndex = m_drawContext->GetRenderingQueueFamilyIndex();
VkBool32 supportsPresent;
statusCode = vkGetPhysicalDeviceSurfaceSupportKHR(m_gpu, renderingQueueIndex, m_surface, &supportsPresent);
if (statusCode != VK_SUCCESS)
{
LOG_ERROR_VK_CALL(vkGetPhysicalDeviceSurfaceSupportKHR, statusCode);
return;
}
CHECK_EQUAL(supportsPresent, VK_TRUE, ());
CHECK(QuerySurfaceSize(), ());
if (m_drawContext)
m_drawContext->SetSurface(m_surface, m_surfaceFormat, m_surfaceCapabilities);
}
void ResetSurface()
{
if (m_drawContext)
m_drawContext->ResetSurface(false);
vkDestroySurfaceKHR(m_vulkanInstance, m_surface, nullptr);
}
};
class MacGLContext : public dp::OGLContext
{
public:
MacGLContext(MacGLContext * contextToShareWith)
: m_viewSet(false)
{
NSOpenGLPixelFormatAttribute attributes[] = {
NSOpenGLPFAAccelerated,
NSOpenGLPFAOpenGLProfile,
NSOpenGLProfileVersion4_1Core,
NSOpenGLPFADoubleBuffer,
NSOpenGLPFAColorSize,
24,
NSOpenGLPFAAlphaSize,
8,
NSOpenGLPFADepthSize,
24,
NSOpenGLPFAStencilSize,
8,
0
};
m_pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes];
CHECK(m_pixelFormat, ("Pixel format is not found"));
m_context = [[NSOpenGLContext alloc] initWithFormat:m_pixelFormat
shareContext:(contextToShareWith ? contextToShareWith->m_context : nil)];
int interval = 1;
[m_context getValues:&interval forParameter:NSOpenGLContextParameterSwapInterval];
}
~MacGLContext()
{
@autoreleasepool {
[m_context clearDrawable];
m_pixelFormat = nil;
m_context = nil;
}
}
bool BeginRendering() override { return m_viewSet; }
void Present() override
{
if (m_viewSet)
{
std::lock_guard<std::mutex> lock(m_updateSizeMutex);
[m_context flushBuffer];
}
}
void MakeCurrent() override
{
[m_context makeCurrentContext];
}
void DoneCurrent() override
{
[NSOpenGLContext clearCurrentContext];
}
void SetFramebuffer(ref_ptr<dp::BaseFramebuffer> framebuffer) override
{
if (framebuffer)
framebuffer->Bind();
else
GLFunctions::glBindFramebuffer(0);
}
void SetView(NSView* view)
{
[m_context setView: view];
[m_context update];
m_viewSet = true;
}
void UpdateSize(int w, int h)
{
std::lock_guard<std::mutex> lock(m_updateSizeMutex);
[m_context update];
}
private:
NSOpenGLPixelFormat * m_pixelFormat = nil;
NSOpenGLContext* m_context = nil;
std::atomic<bool> m_viewSet;
std::mutex m_updateSizeMutex;
};
class MacGLContextFactory: public dp::GraphicsContextFactory
{
public:
dp::GraphicsContext * GetDrawContext() override
{
bool needNotify = false;
{
std::lock_guard<std::mutex> lock(m_contextAccess);
if (m_drawContext == nullptr)
{
m_drawContext = std::make_unique<MacGLContext>(m_uploadContext.get());
needNotify = true;
}
}
if (needNotify)
NotifyView();
std::lock_guard<std::mutex> lock(m_contextAccess);
return m_drawContext.get();
}
dp::GraphicsContext * GetResourcesUploadContext() override
{
std::lock_guard<std::mutex> lock(m_contextAccess);
if (m_uploadContext == nullptr)
m_uploadContext = std::make_unique<MacGLContext>(m_drawContext.get());
return m_uploadContext.get();
}
void WaitForInitialization(dp::GraphicsContext *) override
{
std::unique_lock<std::mutex> lock(m_initializationMutex);
if (m_isInitialized)
return;
m_initializationCounter++;
if (m_initializationCounter >= kGLThreadsCount)
{
m_isInitialized = true;
m_initializationCondition.notify_all();
}
else
{
m_initializationCondition.wait(lock, [this] { return m_isInitialized; });
}
}
bool IsDrawContextCreated() const override
{
std::lock_guard<std::mutex> lock(m_contextAccess);
return m_drawContext != nullptr;
}
bool IsUploadContextCreated() const override
{
std::lock_guard<std::mutex> lock(m_contextAccess);
return m_uploadContext != nullptr;
}
void SetView(NSView* view)
{
bool needWait;
{
std::lock_guard<std::mutex> lock(m_contextAccess);
needWait = (m_drawContext == nullptr);
}
if (needWait)
{
std::unique_lock<std::mutex> lock(m_viewSetMutex);
m_viewSetCondition.wait(lock, [this] { return m_viewSet; });
}
std::lock_guard<std::mutex> lock(m_contextAccess);
CHECK(m_drawContext, ());
m_drawContext->SetView(view);
}
void UpdateSize(int w, int h)
{
std::lock_guard<std::mutex> lock(m_contextAccess);
if (m_drawContext)
m_drawContext->UpdateSize(w, h);
}
private:
void NotifyView()
{
std::lock_guard<std::mutex> lock(m_viewSetMutex);
m_viewSet = true;
m_viewSetCondition.notify_all();
}
static size_t constexpr kGLThreadsCount = 2;
std::unique_ptr<MacGLContext> m_drawContext;
std::unique_ptr<MacGLContext> m_uploadContext;
mutable std::mutex m_contextAccess;
bool m_isInitialized = false;
size_t m_initializationCounter = 0;
std::condition_variable m_initializationCondition;
std::mutex m_initializationMutex;
bool m_viewSet = false;
std::condition_variable m_viewSetCondition;
std::mutex m_viewSetMutex;
};
drape_ptr<dp::GraphicsContextFactory> CreateContextFactory(GLFWwindow *window, dp::ApiVersion api, m2::PointU size)
{
if (api == dp::ApiVersion::Metal)
{
CAMetalLayer *layer = [CAMetalLayer layer];
layer.device = MTLCreateSystemDefaultDevice();
layer.opaque = YES;
layer.displaySyncEnabled = YES;
NSWindow *nswindow = glfwGetCocoaWindow(window);
NSScreen *screen = [NSScreen mainScreen];
CGFloat factor = [screen backingScaleFactor];
layer.contentsScale = factor;
nswindow.contentView.layer = layer;
nswindow.contentView.wantsLayer = YES;
return make_unique_dp<MetalContextFactory>(layer, size);
}
if (api == dp::ApiVersion::Vulkan)
{
CAMetalLayer *layer = [CAMetalLayer layer];
layer.device = MTLCreateSystemDefaultDevice();
layer.opaque = YES;
layer.displaySyncEnabled = YES;
NSWindow *nswindow = glfwGetCocoaWindow(window);
NSScreen *screen = [NSScreen mainScreen];
CGFloat factor = [screen backingScaleFactor];
layer.contentsScale = factor;
nswindow.contentView.layer = layer;
nswindow.contentView.wantsLayer = YES;
auto contextFactory = make_unique_dp<MacOSVulkanContextFactory>();
contextFactory->SetSurface(layer);
return contextFactory;
}
if (api == dp::ApiVersion::OpenGLES3)
{
NSWindow *nswindow = glfwGetCocoaWindow(window);
[nswindow.contentView setWantsBestResolutionOpenGLSurface:YES];
return make_unique_dp<MacGLContextFactory>();
}
ASSERT(false, ("API is not available yet"));
return nullptr;
}
void OnCreateDrapeEngine(GLFWwindow *window, dp::ApiVersion api,
ref_ptr<dp::GraphicsContextFactory> contextFactory)
{
if (api == dp::ApiVersion::OpenGLES3)
{
NSWindow *nswindow = glfwGetCocoaWindow(window);
ref_ptr<MacGLContextFactory> macosContextFactory = contextFactory;
macosContextFactory->SetView(nswindow.contentView);
}
}
void PrepareDestroyContextFactory(ref_ptr<dp::GraphicsContextFactory> contextFactory)
{
auto const api = contextFactory->GetDrawContext()->GetApiVersion();
if (api == dp::ApiVersion::Metal || api == dp::ApiVersion::OpenGLES3)
{
// Do nothing
}
else if (api == dp::ApiVersion::Vulkan)
{
ref_ptr<MacOSVulkanContextFactory> macosContextFactory = contextFactory;
macosContextFactory->ResetSurface();
}
else
{
ASSERT(false, ("API is not available yet"));
}
}
void UpdateContentScale(GLFWwindow *window, float scale)
{
NSWindow *nswindow = glfwGetCocoaWindow(window);
if (nswindow.contentView.layer)
nswindow.contentView.layer.contentsScale = scale;
}
void UpdateSize(ref_ptr<dp::GraphicsContextFactory> contextFactory, int w, int h)
{
if (!contextFactory || !contextFactory->GetDrawContext())
return;
auto const api = contextFactory->GetDrawContext()->GetApiVersion();
if (api == dp::ApiVersion::OpenGLES3)
{
ref_ptr<MacGLContextFactory> macosContextFactory = contextFactory;
macosContextFactory->UpdateSize(w, h);
}
}

411
dev_sandbox/main_linux.cpp Normal file
View file

@ -0,0 +1,411 @@
#include "std/target_os.hpp"
#if !defined(OMIM_OS_LINUX)
#error Unsupported OS
#endif
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
#define GLFW_EXPOSE_NATIVE_X11
#include <GLFW/glfw3native.h>
#include <dlfcn.h>
#include <X11/X.h>
#include <vulkan_wrapper.h>
#include <vulkan/vulkan_xlib.h>
// Workaround for TestFunction::Always compilation issue:
// /usr/include/X11/X.h:441:33: note: expanded from macro 'Always'
#undef Always
// Workaround for storage::Status compilation issue:
// /usr/include/X11/Xlib.h:83:16: note: expanded from macro 'Status'
#undef Status
#include "drape/vulkan/vulkan_context_factory.hpp"
#include "drape/gl_functions.hpp"
#include "drape/gl_includes.hpp"
#include "drape/oglcontext.hpp"
#include <array>
#include <atomic>
#include <memory>
class LinuxVulkanContextFactory : public dp::vulkan::VulkanContextFactory
{
public:
LinuxVulkanContextFactory() : dp::vulkan::VulkanContextFactory(1, 33, false) {}
void SetSurface(Display * display, Window window)
{
VkXlibSurfaceCreateInfoKHR const createInfo = {
.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR,
.pNext = nullptr,
.flags = 0,
.dpy = display,
.window = window,
};
VkResult statusCode;
CHECK(vkCreateXlibSurfaceKHR, ());
statusCode = vkCreateXlibSurfaceKHR(m_vulkanInstance, &createInfo, nullptr, &m_surface);
if (statusCode != VK_SUCCESS)
{
LOG_ERROR_VK_CALL(vkCreateXlibSurfaceKHR, statusCode);
return;
}
uint32_t const renderingQueueIndex = m_drawContext->GetRenderingQueueFamilyIndex();
VkBool32 supportsPresent;
statusCode = vkGetPhysicalDeviceSurfaceSupportKHR(m_gpu, renderingQueueIndex, m_surface, &supportsPresent);
if (statusCode != VK_SUCCESS)
{
LOG_ERROR_VK_CALL(vkGetPhysicalDeviceSurfaceSupportKHR, statusCode);
return;
}
CHECK_EQUAL(supportsPresent, VK_TRUE, ());
CHECK(QuerySurfaceSize(), ());
if (m_drawContext)
m_drawContext->SetSurface(m_surface, m_surfaceFormat, m_surfaceCapabilities);
}
void ResetSurface()
{
if (m_drawContext)
m_drawContext->ResetSurface(false);
vkDestroySurfaceKHR(m_vulkanInstance, m_surface, nullptr);
}
};
// Based on: https://github.com/glfw/glfw/blob/master/src/glx_context.c
#define GLX_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001
#define GLX_CONTEXT_PROFILE_MASK_ARB 0x9126
#define GLX_CONTEXT_MAJOR_VERSION_ARB 0x2091
#define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092
#define GLX_PBUFFER_HEIGHT 0x8040
#define GLX_PBUFFER_WIDTH 0x8041
#define GLX_DOUBLEBUFFER 5
#define GLX_DRAWABLE_TYPE 0x8010
#define GLX_RENDER_TYPE 0x8011
#define GLX_WINDOW_BIT 0x00000001
#define GLX_PBUFFER_BIT 0x00000004
#define GLX_RGBA_BIT 0x00000001
#define GLX_RED_SIZE 8
#define GLX_GREEN_SIZE 9
#define GLX_BLUE_SIZE 10
#define GLX_ALPHA_SIZE 11
#define GLX_DEPTH_SIZE 12
#define GLX_STENCIL_SIZE 13
typedef XID GLXDrawable;
typedef struct __GLXcontext * GLXContext;
typedef XID GLXPbuffer;
typedef struct __GLXFBConfig * GLXFBConfig;
typedef void (*__GLXextproc)(void);
typedef __GLXextproc (*PFNGLXGETPROCADDRESSPROC)(const GLubyte * procName);
typedef int (*PFNXFREE)(void *);
typedef GLXFBConfig * (*PFNGLXCHOOSEFBCONFIGPROC)(Display *, int, const int *, int *);
typedef GLXContext (*PFNGLXCREATECONTEXTATTRIBSARB)(Display *, GLXFBConfig, GLXContext, Bool, const int *);
typedef void (*PFNGLXDESTROYCONTEXT)(Display *, GLXContext);
typedef GLXPbuffer (*PFNGLXCREATEPBUFFERPROC)(Display *, GLXFBConfig, const int *);
typedef void (*PFNGLXDESTROYPBUFFER)(Display *, GLXPbuffer);
typedef Bool (*PFNGLXMAKECURRENTPROC)(Display *, GLXDrawable, GLXContext);
typedef void (*PFNGLXSWAPBUFFERSPROC)(Display *, GLXDrawable);
struct GLXFunctions
{
GLXFunctions()
{
std::array<char const *, 3> libs = {
"libGLX.so.0",
"libGL.so.1",
"libGL.so",
};
for (char const * lib : libs)
{
m_module = dlopen(lib, RTLD_LAZY | RTLD_LOCAL);
if (m_module)
{
break;
}
}
CHECK(m_module != nullptr, ("Failed to initialize GLX"));
XFree = loadFunction<PFNXFREE>("XFree");
glXGetProcAddress = loadFunction<PFNGLXGETPROCADDRESSPROC>("glXGetProcAddress");
glXGetProcAddressARB = loadFunction<PFNGLXGETPROCADDRESSPROC>("glXGetProcAddressARB");
glXChooseFBConfig = loadGlxFunction<PFNGLXCHOOSEFBCONFIGPROC>("glXChooseFBConfig");
glXCreateContextAttribsARB = loadGlxFunction<PFNGLXCREATECONTEXTATTRIBSARB>("glXCreateContextAttribsARB");
glXDestroyContext = loadGlxFunction<PFNGLXDESTROYCONTEXT>("glXDestroyContext");
glXCreatePbuffer = loadGlxFunction<PFNGLXCREATEPBUFFERPROC>("glXCreatePbuffer");
glXDestroyPbuffer = loadGlxFunction<PFNGLXDESTROYPBUFFER>("glXDestroyPbuffer");
glXMakeCurrent = loadGlxFunction<PFNGLXMAKECURRENTPROC>("glXMakeCurrent");
glXSwapBuffers = loadGlxFunction<PFNGLXSWAPBUFFERSPROC>("glXSwapBuffers");
}
~GLXFunctions()
{
if (m_module)
{
dlclose(m_module);
}
}
PFNXFREE XFree = nullptr;
PFNGLXGETPROCADDRESSPROC glXGetProcAddress = nullptr;
PFNGLXGETPROCADDRESSPROC glXGetProcAddressARB = nullptr;
PFNGLXCHOOSEFBCONFIGPROC glXChooseFBConfig = nullptr;
PFNGLXCREATECONTEXTATTRIBSARB glXCreateContextAttribsARB = nullptr;
PFNGLXDESTROYCONTEXT glXDestroyContext = nullptr;
PFNGLXCREATEPBUFFERPROC glXCreatePbuffer = nullptr;
PFNGLXDESTROYPBUFFER glXDestroyPbuffer = nullptr;
PFNGLXMAKECURRENTPROC glXMakeCurrent = nullptr;
PFNGLXSWAPBUFFERSPROC glXSwapBuffers = nullptr;
private:
template <typename T>
T loadFunction(char const * func)
{
auto f = reinterpret_cast<T>(dlsym(m_module, func));
ASSERT(f, ("Failed to initialize GLX:", func, "is not found"));
return f;
}
template <typename T>
T loadGlxFunction(char const * func)
{
if (auto f = reinterpret_cast<T>(glXGetProcAddress(reinterpret_cast<GLubyte const *>(func))))
return f;
if (auto f = reinterpret_cast<T>(glXGetProcAddressARB(reinterpret_cast<GLubyte const *>(func))))
return f;
return loadFunction<T>(func);
}
void * m_module = nullptr;
};
class LinuxGLContext : public dp::OGLContext
{
public:
LinuxGLContext(GLXFunctions const & glx, Display * display, Window window, LinuxGLContext * contextToShareWith,
bool usePixelBuffer)
: m_glx(glx), m_display(display), m_window(window)
{
int visualAttribs[] = {
GLX_DOUBLEBUFFER, True,
GLX_RENDER_TYPE, GLX_RGBA_BIT,
GLX_DRAWABLE_TYPE, (usePixelBuffer ? GLX_PBUFFER_BIT : GLX_WINDOW_BIT),
GLX_RED_SIZE, 8,
GLX_GREEN_SIZE, 8,
GLX_BLUE_SIZE, 8,
GLX_ALPHA_SIZE, 8,
GLX_DEPTH_SIZE, 24,
GLX_STENCIL_SIZE, 8,
None
};
int contextAttribs[] = {
GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB,
GLX_CONTEXT_MAJOR_VERSION_ARB, 4,
GLX_CONTEXT_MINOR_VERSION_ARB, 1,
None};
int fbcount = 0;
if (GLXFBConfig * config = m_glx.glXChooseFBConfig(display, DefaultScreen(display), visualAttribs, &fbcount))
{
m_context =
m_glx.glXCreateContextAttribsARB(display, config[0], contextToShareWith ? contextToShareWith->m_context : 0, True, contextAttribs);
CHECK(m_context != nullptr, ("Failed to create GLX context"));
if (usePixelBuffer)
{
int pbufferAttribs[] = {GLX_PBUFFER_WIDTH, 1, GLX_PBUFFER_HEIGHT, 1, None};
m_pixelBufferHandle = m_glx.glXCreatePbuffer(display, config[0], pbufferAttribs);
CHECK(m_pixelBufferHandle != 0, ("Failed to create GLX pbuffer"));
}
m_glx.XFree(config);
}
}
~LinuxGLContext() override
{
if (m_pixelBufferHandle)
{
m_glx.glXDestroyPbuffer(m_display, m_pixelBufferHandle);
m_pixelBufferHandle = 0;
}
if (m_context)
{
m_glx.glXDestroyContext(m_display, m_context);
m_context = nullptr;
}
}
void Present() override
{
if (!m_pixelBufferHandle)
m_glx.glXSwapBuffers(m_display, m_window);
}
void MakeCurrent() override
{
if (!m_glx.glXMakeCurrent(m_display, m_pixelBufferHandle ? m_pixelBufferHandle : m_window, m_context))
LOG(LERROR, ("MakeCurrent(): glXMakeCurrent failed"));
}
void DoneCurrent() override
{
if (!m_glx.glXMakeCurrent(m_display, None, nullptr))
LOG(LERROR, ("DoneCurrent(): glXMakeCurrent failed"));
}
void SetFramebuffer(ref_ptr<dp::BaseFramebuffer> framebuffer) override
{
if (framebuffer)
framebuffer->Bind();
else
GLFunctions::glBindFramebuffer(0);
}
private:
GLXFunctions const & m_glx;
Display * m_display = nullptr;
Window m_window = 0;
GLXDrawable m_pixelBufferHandle = 0;
GLXContext m_context = nullptr;
};
class LinuxContextFactory : public dp::GraphicsContextFactory
{
public:
LinuxContextFactory(Display * display, Window window) : m_display(display), m_window(window) {}
dp::GraphicsContext * GetDrawContext() override
{
std::lock_guard<std::mutex> lock(m_contextAccess);
if (m_drawContext == nullptr)
m_drawContext = std::make_unique<LinuxGLContext>(m_glx, m_display, m_window, m_uploadContext.get(), false);
return m_drawContext.get();
}
dp::GraphicsContext * GetResourcesUploadContext() override
{
std::lock_guard<std::mutex> lock(m_contextAccess);
if (m_uploadContext == nullptr)
m_uploadContext = std::make_unique<LinuxGLContext>(m_glx, m_display, 0, m_drawContext.get(), true);
return m_uploadContext.get();
}
void WaitForInitialization(dp::GraphicsContext *) override
{
std::unique_lock<std::mutex> lock(m_initializationMutex);
if (m_isInitialized)
return;
m_initializationCounter++;
if (m_initializationCounter >= kGLThreadsCount)
{
m_isInitialized = true;
m_initializationCondition.notify_all();
}
else
{
m_initializationCondition.wait(lock, [this] { return m_isInitialized; });
}
}
bool IsDrawContextCreated() const override
{
std::lock_guard<std::mutex> lock(m_contextAccess);
return m_drawContext != nullptr;
}
bool IsUploadContextCreated() const override
{
std::lock_guard<std::mutex> lock(m_contextAccess);
return m_uploadContext != nullptr;
}
private:
static size_t constexpr kGLThreadsCount = 2;
GLXFunctions m_glx;
Display * m_display = nullptr;
Window m_window = 0;
std::unique_ptr<LinuxGLContext> m_drawContext;
std::unique_ptr<LinuxGLContext> m_uploadContext;
mutable std::mutex m_contextAccess;
bool m_isInitialized = false;
size_t m_initializationCounter = 0;
std::condition_variable m_initializationCondition;
std::mutex m_initializationMutex;
};
drape_ptr<dp::GraphicsContextFactory> CreateContextFactory(GLFWwindow * window, dp::ApiVersion api, m2::PointU size)
{
if (api == dp::ApiVersion::Vulkan)
{
auto contextFactory = make_unique_dp<LinuxVulkanContextFactory>();
contextFactory->SetSurface(glfwGetX11Display(), glfwGetX11Window(window));
return contextFactory;
}
if (api == dp::ApiVersion::OpenGLES3)
{
return make_unique_dp<LinuxContextFactory>(glfwGetX11Display(), glfwGetX11Window(window));
}
ASSERT(false, ("API is not available yet"));
return nullptr;
}
void OnCreateDrapeEngine(GLFWwindow * window, dp::ApiVersion api, ref_ptr<dp::GraphicsContextFactory> contextFactory)
{
// Do nothing
}
void PrepareDestroyContextFactory(ref_ptr<dp::GraphicsContextFactory> contextFactory)
{
auto const api = contextFactory->GetDrawContext()->GetApiVersion();
if (api == dp::ApiVersion::OpenGLES3)
{
// Do nothing
}
else if (api == dp::ApiVersion::Vulkan)
{
ref_ptr<LinuxVulkanContextFactory> linuxContextFactory = contextFactory;
linuxContextFactory->ResetSurface();
}
else
{
ASSERT(false, ("API is not available yet"));
}
}
void UpdateContentScale(GLFWwindow * window, float scale)
{
// Do nothing
}
void UpdateSize(ref_ptr<dp::GraphicsContextFactory> contextFactory, int w, int h)
{
// Do nothing
}

View file

@ -84,6 +84,7 @@ Automatically [generated](TRANSLATIONS.md#technical-details):
- `cmake/` - CMake helper files.
- `coding/` - I/O classes and data processing.
- `descriptions/` -
- `dev_sandbox/` - developer tool for debugging rendering.
- `drape_frontend/` - scene and resource manager for the Drape library.
- `drape/` - the new graphics library core.
- `editor/` - built-in OSM data editor.

View file

@ -6,6 +6,7 @@
#include <functional>
#include <string>
#include <variant>
#include <vector>
namespace osm