diff --git a/.gitmodules b/.gitmodules index 81c8ed32..2bba2042 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,3 +13,6 @@ [submodule "external/freetype-gl"] path = external/freetype-gl url = https://github.com/rougier/freetype-gl.git +[submodule "external/stb"] + path = external/stb + url = https://github.com/nothings/stb.git diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index 37fbfe4c..221957b0 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -6,8 +6,10 @@ set_property(DIRECTORY PROPERTY CMAKE_CONFIGURE_DEPENDS freetype-gl.cmake hsluv-c.cmake - mapbox-gl-native.cmake) + mapbox-gl-native.cmake + stb.cmake) include(freetype-gl.cmake) include(hsluv-c.cmake) include(mapbox-gl-native.cmake) +include(stb.cmake) diff --git a/external/stb b/external/stb new file mode 160000 index 00000000..8b5f1f37 --- /dev/null +++ b/external/stb @@ -0,0 +1 @@ +Subproject commit 8b5f1f37b5b75829fc72d38e7b5d4bcbf8a26d55 diff --git a/external/stb.cmake b/external/stb.cmake new file mode 100644 index 00000000..52bbf5f2 --- /dev/null +++ b/external/stb.cmake @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 3.19) +set(PROJECT_NAME scwx-stb) + +set(STB_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/stb PARENT_SCOPE) diff --git a/scwx-qt/gl/geo_line.vert b/scwx-qt/gl/geo_line.vert new file mode 100644 index 00000000..ea7d6351 --- /dev/null +++ b/scwx-qt/gl/geo_line.vert @@ -0,0 +1,41 @@ +#version 330 core + +#define DEGREES_MAX 360.0f +#define LATITUDE_MAX 85.051128779806604f +#define LONGITUDE_MAX 180.0f +#define PI 3.1415926535897932384626433f +#define RAD2DEG 57.295779513082320876798156332941f + +layout (location = 0) in vec2 aLatLong; +layout (location = 1) in vec2 aXYOffset; +layout (location = 2) in vec2 aTexCoord; +layout (location = 3) in vec4 aModulate; + +uniform mat4 uMVPMatrix; +uniform mat4 uMapMatrix; +uniform vec2 uMapScreenCoord; + +smooth out vec2 texCoord; +flat out vec4 modulate; + +vec2 latLngToScreenCoordinate(in vec2 latLng) +{ + vec2 p; + latLng.x = clamp(latLng.x, -LATITUDE_MAX, LATITUDE_MAX); + p.xy = vec2(LONGITUDE_MAX + latLng.y, + -(LONGITUDE_MAX - RAD2DEG * log(tan(PI / 4 + latLng.x * PI / DEGREES_MAX)))); + return p; +} + +void main() +{ + // Pass the texture coordinate and color modulate to the fragment shader + texCoord = aTexCoord; + modulate = aModulate; + + vec2 p = latLngToScreenCoordinate(aLatLong) - uMapScreenCoord; + + // Transform the position to screen coordinates + gl_Position = uMapMatrix * vec4(p, 0.0f, 1.0f) - + uMVPMatrix * vec4(aXYOffset, 0.0f, 0.0f); +} diff --git a/scwx-qt/gl/texture2d.frag b/scwx-qt/gl/texture2d.frag new file mode 100644 index 00000000..16ab8960 --- /dev/null +++ b/scwx-qt/gl/texture2d.frag @@ -0,0 +1,16 @@ +#version 330 core + +// Lower the default precision to medium +precision mediump float; + +uniform sampler2D uTexture; + +smooth in vec2 texCoord; +flat in vec4 modulate; + +layout (location = 0) out vec4 fragColor; + +void main() +{ + fragColor = texture(uTexture, texCoord) * modulate; +} diff --git a/scwx-qt/res/textures/lines/default-1x7.png b/scwx-qt/res/textures/lines/default-1x7.png new file mode 100644 index 00000000..0b72d67c Binary files /dev/null and b/scwx-qt/res/textures/lines/default-1x7.png differ diff --git a/scwx-qt/res/textures/lines/test-pattern.png b/scwx-qt/res/textures/lines/test-pattern.png new file mode 100644 index 00000000..b9a0224e Binary files /dev/null and b/scwx-qt/res/textures/lines/test-pattern.png differ diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index 00abbaf4..51ba42cf 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -40,14 +40,19 @@ set(SRC_MAIN source/scwx/qt/main/main_window.cpp) set(UI_MAIN source/scwx/qt/main/main_window.ui) set(HDR_CONFIG source/scwx/qt/config/radar_site.hpp) set(SRC_CONFIG source/scwx/qt/config/radar_site.cpp) +set(SRC_EXTERNAL source/scwx/qt/external/stb_rect_pack.cpp) set(HDR_GL source/scwx/qt/gl/gl.hpp + source/scwx/qt/gl/gl_context.hpp source/scwx/qt/gl/shader_program.hpp source/scwx/qt/gl/text_shader.hpp) -set(SRC_GL source/scwx/qt/gl/shader_program.cpp +set(SRC_GL source/scwx/qt/gl/gl_context.cpp + source/scwx/qt/gl/shader_program.cpp source/scwx/qt/gl/text_shader.cpp) set(HDR_GL_DRAW source/scwx/qt/gl/draw/draw_item.hpp + source/scwx/qt/gl/draw/geo_line.hpp source/scwx/qt/gl/draw/rectangle.hpp) set(SRC_GL_DRAW source/scwx/qt/gl/draw/draw_item.cpp + source/scwx/qt/gl/draw/geo_line.cpp source/scwx/qt/gl/draw/rectangle.cpp) set(HDR_MANAGER source/scwx/qt/manager/radar_product_manager.hpp source/scwx/qt/manager/radar_product_manager_notifier.hpp @@ -71,6 +76,7 @@ set(SRC_MAP source/scwx/qt/map/color_table_layer.cpp source/scwx/qt/map/draw_layer.cpp source/scwx/qt/map/generic_layer.cpp source/scwx/qt/map/layer_wrapper.cpp + source/scwx/qt/map/map_context.cpp source/scwx/qt/map/map_widget.cpp source/scwx/qt/map/overlay_layer.cpp source/scwx/qt/map/radar_product_layer.cpp @@ -101,10 +107,13 @@ set(SRC_UI source/scwx/qt/ui/flow_layout.cpp source/scwx/qt/ui/level3_products_widget.cpp) set(HDR_UTIL source/scwx/qt/util/font.hpp source/scwx/qt/util/font_buffer.hpp - source/scwx/qt/util/json.hpp) + source/scwx/qt/util/json.hpp + source/scwx/qt/util/streams.hpp + source/scwx/qt/util/texture_atlas.hpp) set(SRC_UTIL source/scwx/qt/util/font.cpp source/scwx/qt/util/font_buffer.cpp - source/scwx/qt/util/json.cpp) + source/scwx/qt/util/json.cpp + source/scwx/qt/util/texture_atlas.cpp) set(HDR_VIEW source/scwx/qt/view/level2_product_view.hpp source/scwx/qt/view/level3_product_view.hpp source/scwx/qt/view/level3_radial_view.hpp @@ -122,12 +131,14 @@ set(RESOURCE_FILES scwx-qt.qrc) set(SHADER_FILES gl/color.frag gl/color.vert + gl/geo_line.vert gl/radar.frag gl/radar.vert gl/text.frag gl/text.vert gl/texture1d.frag - gl/texture1d.vert) + gl/texture1d.vert + gl/texture2d.frag) set(CMAKE_FILES scwx-qt.cmake) @@ -139,6 +150,7 @@ set(PROJECT_SOURCES ${HDR_MAIN} ${SRC_MAIN} ${HDR_CONFIG} ${SRC_CONFIG} + ${SRC_EXTERNAL} ${HDR_GL} ${SRC_GL} ${HDR_GL_DRAW} @@ -173,6 +185,7 @@ source_group("Header Files\\main" FILES ${HDR_MAIN}) source_group("Source Files\\main" FILES ${SRC_MAIN}) source_group("Header Files\\config" FILES ${HDR_CONFIG}) source_group("Source Files\\config" FILES ${SRC_CONFIG}) +source_group("Source Files\\external" FILES ${SRC_EXTERNAL}) source_group("Header Files\\gl" FILES ${HDR_GL}) source_group("Source Files\\gl" FILES ${SRC_GL}) source_group("Header Files\\gl\\draw" FILES ${HDR_GL_DRAW}) @@ -223,7 +236,8 @@ endif() target_include_directories(scwx-qt PUBLIC ${scwx-qt_SOURCE_DIR}/source ${FTGL_INCLUDE_DIR} - ${MBGL_INCLUDE_DIR}) + ${MBGL_INCLUDE_DIR} + ${STB_INCLUDE_DIR}) target_include_directories(supercell-wx PUBLIC ${scwx-qt_SOURCE_DIR}/source) diff --git a/scwx-qt/scwx-qt.qrc b/scwx-qt/scwx-qt.qrc index d3386855..cf31a039 100644 --- a/scwx-qt/scwx-qt.qrc +++ b/scwx-qt/scwx-qt.qrc @@ -2,16 +2,20 @@ gl/color.frag gl/color.vert + gl/geo_line.vert gl/radar.frag gl/radar.vert gl/text.frag gl/text.vert gl/texture1d.frag gl/texture1d.vert + gl/texture2d.frag res/config/radar_sites.json res/fonts/din1451alt.ttf res/fonts/din1451alt_g.ttf res/icons/font-awesome-6/square-minus-regular.svg res/icons/font-awesome-6/square-plus-regular.svg + res/textures/lines/default-1x7.png + res/textures/lines/test-pattern.png diff --git a/scwx-qt/source/scwx/qt/external/stb_rect_pack.cpp b/scwx-qt/source/scwx/qt/external/stb_rect_pack.cpp new file mode 100644 index 00000000..32d48134 --- /dev/null +++ b/scwx-qt/source/scwx/qt/external/stb_rect_pack.cpp @@ -0,0 +1,2 @@ +#define STB_RECT_PACK_IMPLEMENTATION +#include diff --git a/scwx-qt/source/scwx/qt/gl/draw/draw_item.cpp b/scwx-qt/source/scwx/qt/gl/draw/draw_item.cpp index ca4533b6..23ddbc8a 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/draw_item.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/draw_item.cpp @@ -2,6 +2,13 @@ #include +#pragma warning(push, 0) +#include +#include +#include +#include +#pragma warning(pop) + namespace scwx { namespace qt @@ -13,20 +20,76 @@ namespace draw static const std::string logPrefix_ = "scwx::qt::gl::draw::draw_item"; -class DrawItemImpl +class DrawItem::Impl { public: - explicit DrawItemImpl() {} + explicit Impl(OpenGLFunctions& gl) : gl_ {gl} {} + ~Impl() {} - ~DrawItemImpl() {} + OpenGLFunctions& gl_; }; -DrawItem::DrawItem() : p(std::make_unique()) {} +DrawItem::DrawItem(OpenGLFunctions& gl) : p(std::make_unique(gl)) {} DrawItem::~DrawItem() = default; -DrawItem::DrawItem(DrawItem&&) noexcept = default; +DrawItem::DrawItem(DrawItem&&) noexcept = default; DrawItem& DrawItem::operator=(DrawItem&&) noexcept = default; +void DrawItem::UseDefaultProjection( + const QMapbox::CustomLayerRenderParameters& params, GLint uMVPMatrixLocation) +{ + glm::mat4 projection = glm::ortho(0.0f, + static_cast(params.width), + 0.0f, + static_cast(params.height)); + + p->gl_.glUniformMatrix4fv( + uMVPMatrixLocation, 1, GL_FALSE, glm::value_ptr(projection)); +} + +// TODO: Refactor to utility class +static glm::vec2 +LatLongToScreenCoordinate(const QMapbox::Coordinate& coordinate) +{ + double latitude = std::clamp( + coordinate.first, -mbgl::util::LATITUDE_MAX, mbgl::util::LATITUDE_MAX); + glm::vec2 screen { + mbgl::util::LONGITUDE_MAX + coordinate.second, + -(mbgl::util::LONGITUDE_MAX - + mbgl::util::RAD2DEG * + std::log(std::tan(M_PI / 4.0 + + latitude * M_PI / mbgl::util::DEGREES_MAX)))}; + return screen; +} + +void DrawItem::UseMapProjection( + const QMapbox::CustomLayerRenderParameters& params, + GLint uMVPMatrixLocation, + GLint uMapScreenCoordLocation) +{ + OpenGLFunctions& gl = p->gl_; + + // TODO: Refactor to utility class + const float scale = std::pow(2.0, params.zoom) * 2.0f * + mbgl::util::tileSize / mbgl::util::DEGREES_MAX; + const float xScale = scale / params.width; + const float yScale = scale / params.height; + + glm::mat4 uMVPMatrix(1.0f); + uMVPMatrix = glm::scale(uMVPMatrix, glm::vec3(xScale, yScale, 1.0f)); + uMVPMatrix = glm::rotate(uMVPMatrix, + glm::radians(params.bearing), + glm::vec3(0.0f, 0.0f, 1.0f)); + + gl.glUniform2fv(uMapScreenCoordLocation, + 1, + glm::value_ptr(LatLongToScreenCoordinate( + {params.latitude, params.longitude}))); + + gl.glUniformMatrix4fv( + uMVPMatrixLocation, 1, GL_FALSE, glm::value_ptr(uMVPMatrix)); +} + } // namespace draw } // namespace gl } // namespace qt diff --git a/scwx-qt/source/scwx/qt/gl/draw/draw_item.hpp b/scwx-qt/source/scwx/qt/gl/draw/draw_item.hpp index 09e98092..02086c45 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/draw_item.hpp +++ b/scwx-qt/source/scwx/qt/gl/draw/draw_item.hpp @@ -1,7 +1,11 @@ #pragma once +#include + #include +#include + namespace scwx { namespace qt @@ -11,26 +15,33 @@ namespace gl namespace draw { -class DrawItemImpl; - class DrawItem { public: - explicit DrawItem(); + explicit DrawItem(OpenGLFunctions& gl); ~DrawItem(); - DrawItem(const DrawItem&) = delete; + DrawItem(const DrawItem&) = delete; DrawItem& operator=(const DrawItem&) = delete; DrawItem(DrawItem&&) noexcept; DrawItem& operator=(DrawItem&&) noexcept; - virtual void Initialize() = 0; - virtual void Render() = 0; - virtual void Deinitialize() = 0; + virtual void Initialize() = 0; + virtual void Render(const QMapbox::CustomLayerRenderParameters& params) = 0; + virtual void Deinitialize() = 0; + +protected: + void UseDefaultProjection(const QMapbox::CustomLayerRenderParameters& params, + GLint uMVPMatrixLocation); + void UseMapProjection(const QMapbox::CustomLayerRenderParameters& params, + GLint uMVPMatrixLocation, + GLint uMapScreenCoordLocation); private: - std::unique_ptr p; + class Impl; + + std::unique_ptr p; }; } // namespace draw diff --git a/scwx-qt/source/scwx/qt/gl/draw/geo_line.cpp b/scwx-qt/source/scwx/qt/gl/draw/geo_line.cpp new file mode 100644 index 00000000..b44463ff --- /dev/null +++ b/scwx-qt/source/scwx/qt/gl/draw/geo_line.cpp @@ -0,0 +1,292 @@ +#include +#include +#include +#include + +#include + +namespace scwx +{ +namespace qt +{ +namespace gl +{ +namespace draw +{ + +static const std::string logPrefix_ = "scwx::qt::gl::draw::geo_line"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); + +static constexpr size_t kNumRectangles = 1; +static constexpr size_t kNumTriangles = kNumRectangles * 2; +static constexpr size_t kVerticesPerTriangle = 3; +static constexpr size_t kVerticesPerRectangle = kVerticesPerTriangle * 2; +static constexpr size_t kPointsPerVertex = 10; +static constexpr size_t kBufferLength = + kNumTriangles * kVerticesPerTriangle * kPointsPerVertex; + +class GeoLine::Impl +{ +public: + explicit Impl(std::shared_ptr context) : + context_ {context}, + dirty_ {false}, + visible_ {true}, + points_ {}, + width_ {7.0f}, + modulateColor_ {std::nullopt}, + shaderProgram_ {nullptr}, + uMVPMatrixLocation_(GL_INVALID_INDEX), + uMapMatrixLocation_(GL_INVALID_INDEX), + uMapScreenCoordLocation_(GL_INVALID_INDEX), + texture_ {}, + vao_ {GL_INVALID_INDEX}, + vbo_ {GL_INVALID_INDEX} + { + } + + ~Impl() {} + + std::shared_ptr context_; + + bool dirty_; + + bool visible_; + std::array points_; + float width_; + + std::optional modulateColor_; + + std::shared_ptr shaderProgram_; + GLint uMVPMatrixLocation_; + GLint uMapMatrixLocation_; + GLint uMapScreenCoordLocation_; + + util::TextureAttributes texture_; + + GLuint vao_; + GLuint vbo_; + + void Update(); +}; + +GeoLine::GeoLine(std::shared_ptr context) : + DrawItem(context->gl()), p(std::make_unique(context)) +{ +} +GeoLine::~GeoLine() = default; + +GeoLine::GeoLine(GeoLine&&) noexcept = default; +GeoLine& GeoLine::operator=(GeoLine&&) noexcept = default; + +void GeoLine::Initialize() +{ + gl::OpenGLFunctions& gl = p->context_->gl(); + + p->shaderProgram_ = p->context_->GetShaderProgram(":/gl/geo_line.vert", + ":/gl/texture2d.frag"); + + p->uMVPMatrixLocation_ = + gl.glGetUniformLocation(p->shaderProgram_->id(), "uMVPMatrix"); + if (p->uMVPMatrixLocation_ == -1) + { + logger_->warn("Could not find uMVPMatrix"); + } + + p->uMapMatrixLocation_ = + gl.glGetUniformLocation(p->shaderProgram_->id(), "uMapMatrix"); + if (p->uMapMatrixLocation_ == -1) + { + logger_->warn("Could not find uMapMatrix"); + } + + p->uMapScreenCoordLocation_ = + gl.glGetUniformLocation(p->shaderProgram_->id(), "uMapScreenCoord"); + if (p->uMapScreenCoordLocation_ == -1) + { + logger_->warn("Could not find uMapScreenCoord"); + } + + p->texture_ = + util::TextureAtlas::Instance().GetTextureAttributes("lines/default-1x7"); + + gl.glGenVertexArrays(1, &p->vao_); + gl.glGenBuffers(1, &p->vbo_); + + gl.glBindVertexArray(p->vao_); + gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_); + gl.glBufferData( + GL_ARRAY_BUFFER, sizeof(float) * kBufferLength, nullptr, GL_DYNAMIC_DRAW); + + // aLatLong + gl.glVertexAttribPointer(0, + 2, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + static_cast(0)); + gl.glEnableVertexAttribArray(0); + + // aXYOffset + gl.glVertexAttribPointer(1, + 2, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + reinterpret_cast(2 * sizeof(float))); + gl.glEnableVertexAttribArray(1); + + // aTexCoord + gl.glVertexAttribPointer(2, + 2, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + reinterpret_cast(4 * sizeof(float))); + gl.glEnableVertexAttribArray(2); + + // aModulate + gl.glVertexAttribPointer(3, + 4, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + reinterpret_cast(6 * sizeof(float))); + gl.glEnableVertexAttribArray(3); + + p->dirty_ = true; +} + +void GeoLine::Render(const QMapbox::CustomLayerRenderParameters& params) +{ + if (p->visible_) + { + gl::OpenGLFunctions& gl = p->context_->gl(); + + gl.glBindVertexArray(p->vao_); + gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_); + + p->Update(); + p->shaderProgram_->Use(); + UseDefaultProjection(params, p->uMVPMatrixLocation_); + UseMapProjection( + params, p->uMapMatrixLocation_, p->uMapScreenCoordLocation_); + + // Draw line + gl.glDrawArrays(GL_TRIANGLES, 0, 6); + } +} + +void GeoLine::Deinitialize() +{ + gl::OpenGLFunctions& gl = p->context_->gl(); + + gl.glDeleteVertexArrays(1, &p->vao_); + gl.glDeleteBuffers(1, &p->vbo_); +} + +void GeoLine::SetPoints(float latitude1, + float longitude1, + float latitude2, + float longitude2) +{ + if (p->points_[0].latitude_ != latitude1 || + p->points_[0].longitude_ != longitude1 || + p->points_[1].latitude_ != latitude2 || + p->points_[1].longitude_ != longitude2) + { + p->points_[0] = {latitude1, longitude1}; + p->points_[1] = {latitude2, longitude2}; + p->dirty_ = true; + } +} + +void GeoLine::SetModulateColor(boost::gil::rgba8_pixel_t color) +{ + if (p->modulateColor_ != color) + { + p->modulateColor_ = color; + p->dirty_ = true; + } +} + +void GeoLine::SetWidth(float width) +{ + if (p->width_ != width) + { + p->width_ = width; + p->dirty_ = true; + } +} + +void GeoLine::SetVisible(bool visible) +{ + p->visible_ = visible; +} + +void GeoLine::Impl::Update() +{ + if (dirty_) + { + gl::OpenGLFunctions& gl = context_->gl(); + + // Latitude and longitude coordinates in degrees + const float lx = points_[0].latitude_; + const float rx = points_[1].latitude_; + const float by = points_[0].longitude_; + const float ty = points_[1].longitude_; + + // Offset x/y in pixels + const double i = points_[1].longitude_ - points_[0].longitude_; + const double j = points_[1].latitude_ - points_[0].latitude_; + const double angle = std::atan2(i, j) * 180.0 / M_PI; + const float ox = width_ * 0.5f * std::cosf(angle); + const float oy = width_ * 0.5f * std::sinf(angle); + + // Texture coordinates + const float ls = texture_.sLeft_; + const float rs = texture_.sRight_; + const float tt = texture_.tTop_; + const float bt = texture_.tBottom_; + + float mc0 = 1.0f; + float mc1 = 1.0f; + float mc2 = 1.0f; + float mc3 = 1.0f; + + if (modulateColor_.has_value()) + { + boost::gil::rgba8_pixel_t& mc = modulateColor_.value(); + + mc0 = mc[0] / 255.0f; + mc1 = mc[1] / 255.0f; + mc2 = mc[2] / 255.0f; + mc3 = mc[3] / 255.0f; + } + + const float buffer[kNumRectangles][kVerticesPerRectangle] + [kPointsPerVertex] = // + { // + // Line + { + {lx, by, -ox, -oy, ls, bt, mc0, mc1, mc2, mc3}, // BL + {lx, by, +ox, +oy, ls, tt, mc0, mc1, mc2, mc3}, // TL + {rx, ty, -ox, -oy, rs, bt, mc0, mc1, mc2, mc3}, // BR + {rx, ty, -ox, -oy, rs, bt, mc0, mc1, mc2, mc3}, // BR + {rx, ty, +ox, +oy, rs, tt, mc0, mc1, mc2, mc3}, // TR + {lx, by, +ox, +oy, ls, tt, mc0, mc1, mc2, mc3} // TL + }}; + + gl.glBufferData(GL_ARRAY_BUFFER, + sizeof(float) * kBufferLength, + buffer, + GL_DYNAMIC_DRAW); + + dirty_ = false; + } +} + +} // namespace draw +} // namespace gl +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/gl/draw/geo_line.hpp b/scwx-qt/source/scwx/qt/gl/draw/geo_line.hpp new file mode 100644 index 00000000..9ae14efb --- /dev/null +++ b/scwx-qt/source/scwx/qt/gl/draw/geo_line.hpp @@ -0,0 +1,77 @@ +#pragma once + +#include +#include + +#include + +namespace scwx +{ +namespace qt +{ +namespace gl +{ +namespace draw +{ + +class GeoLine : public DrawItem +{ +public: + explicit GeoLine(std::shared_ptr context); + ~GeoLine(); + + GeoLine(const GeoLine&) = delete; + GeoLine& operator=(const GeoLine&) = delete; + + GeoLine(GeoLine&&) noexcept; + GeoLine& operator=(GeoLine&&) noexcept; + + void Initialize() override; + void Render(const QMapbox::CustomLayerRenderParameters& params) override; + void Deinitialize() override; + + /** + * Sets the geographic coordinate endpoints associated with the line. + * + * @param latitude1 Latitude of the first endpoint in degrees + * @param longitude1 Longitude of the first endpoint in degrees + * @param latitude2 Latitude of the second endpoint in degrees + * @param longitude2 Longitude of the second endpoint in degrees + */ + void SetPoints(float latitude1, + float longitude1, + float latitude2, + float longitude2); + + /** + * Sets the modulate color of the line. If specified, the texture color will + * be multiplied by the modulate color to produce the result. + * + * @param color Modulate color (RGBA) + */ + void SetModulateColor(boost::gil::rgba8_pixel_t color); + + /** + * Sets the width of the line. + * + * @param width Width in pixels + */ + void SetWidth(float width); + + /** + * Sets the visibility of the line. + * + * @param visible + */ + void SetVisible(bool visible); + +private: + class Impl; + + std::unique_ptr p; +}; + +} // namespace draw +} // namespace gl +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/gl/draw/rectangle.cpp b/scwx-qt/source/scwx/qt/gl/draw/rectangle.cpp index 10988613..f8b5756a 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/rectangle.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/rectangle.cpp @@ -1,4 +1,5 @@ #include +#include #include @@ -12,6 +13,7 @@ namespace draw { static const std::string logPrefix_ = "scwx::qt::gl::draw::rectangle"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); static constexpr size_t NUM_RECTANGLES = 5; static constexpr size_t NUM_TRIANGLES = NUM_RECTANGLES * 2; @@ -21,11 +23,11 @@ static constexpr size_t POINTS_PER_VERTEX = 7; static constexpr size_t BUFFER_LENGTH = NUM_TRIANGLES * VERTICES_PER_TRIANGLE * POINTS_PER_VERTEX; -class RectangleImpl +class Rectangle::Impl { public: - explicit RectangleImpl(OpenGLFunctions& gl) : - gl_ {gl}, + explicit Impl(std::shared_ptr context) : + context_ {context}, dirty_ {false}, visible_ {true}, x_ {0.0f}, @@ -36,14 +38,16 @@ public: borderColor_ {0, 0, 0, 0}, borderWidth_ {0.0f}, fillColor_ {std::nullopt}, + shaderProgram_ {nullptr}, + uMVPMatrixLocation_(GL_INVALID_INDEX), vao_ {GL_INVALID_INDEX}, vbo_ {GL_INVALID_INDEX} { } - ~RectangleImpl() {} + ~Impl() {} - OpenGLFunctions& gl_; + std::shared_ptr context_; bool dirty_; @@ -59,25 +63,37 @@ public: std::optional fillColor_; + std::shared_ptr shaderProgram_; + GLint uMVPMatrixLocation_; + GLuint vao_; GLuint vbo_; void Update(); }; -// TODO: OpenGL context with shaders -Rectangle::Rectangle(OpenGLFunctions& gl) : - DrawItem(), p(std::make_unique(gl)) +Rectangle::Rectangle(std::shared_ptr context) : + DrawItem(context->gl()), p(std::make_unique(context)) { } Rectangle::~Rectangle() = default; -Rectangle::Rectangle(Rectangle&&) noexcept = default; +Rectangle::Rectangle(Rectangle&&) noexcept = default; Rectangle& Rectangle::operator=(Rectangle&&) noexcept = default; void Rectangle::Initialize() { - gl::OpenGLFunctions& gl = p->gl_; + gl::OpenGLFunctions& gl = p->context_->gl(); + + p->shaderProgram_ = + p->context_->GetShaderProgram(":/gl/color.vert", ":/gl/color.frag"); + + p->uMVPMatrixLocation_ = + gl.glGetUniformLocation(p->shaderProgram_->id(), "uMVPMatrix"); + if (p->uMVPMatrixLocation_ == -1) + { + logger_->warn("Could not find uMVPMatrix"); + } gl.glGenVertexArrays(1, &p->vao_); gl.glGenBuffers(1, &p->vbo_); @@ -106,16 +122,18 @@ void Rectangle::Initialize() p->dirty_ = true; } -void Rectangle::Render() +void Rectangle::Render(const QMapbox::CustomLayerRenderParameters& params) { if (p->visible_) { - gl::OpenGLFunctions& gl = p->gl_; + gl::OpenGLFunctions& gl = p->context_->gl(); gl.glBindVertexArray(p->vao_); gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_); p->Update(); + p->shaderProgram_->Use(); + UseDefaultProjection(params, p->uMVPMatrixLocation_); if (p->fillColor_.has_value()) { @@ -133,7 +151,7 @@ void Rectangle::Render() void Rectangle::Deinitialize() { - gl::OpenGLFunctions& gl = p->gl_; + gl::OpenGLFunctions& gl = p->context_->gl(); gl.glDeleteVertexArrays(1, &p->vao_); gl.glDeleteBuffers(1, &p->vbo_); @@ -184,11 +202,11 @@ void Rectangle::SetVisible(bool visible) p->visible_ = visible; } -void RectangleImpl::Update() +void Rectangle::Impl::Update() { if (dirty_) { - gl::OpenGLFunctions& gl = gl_; + gl::OpenGLFunctions& gl = context_->gl(); const float lox = x_; const float rox = x_ + width_; diff --git a/scwx-qt/source/scwx/qt/gl/draw/rectangle.hpp b/scwx-qt/source/scwx/qt/gl/draw/rectangle.hpp index f1214309..481f7507 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/rectangle.hpp +++ b/scwx-qt/source/scwx/qt/gl/draw/rectangle.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include @@ -14,22 +14,20 @@ namespace gl namespace draw { -class RectangleImpl; - class Rectangle : public DrawItem { public: - explicit Rectangle(OpenGLFunctions& gl); + explicit Rectangle(std::shared_ptr context); ~Rectangle(); - Rectangle(const Rectangle&) = delete; + Rectangle(const Rectangle&) = delete; Rectangle& operator=(const Rectangle&) = delete; Rectangle(Rectangle&&) noexcept; Rectangle& operator=(Rectangle&&) noexcept; void Initialize() override; - void Render() override; + void Render(const QMapbox::CustomLayerRenderParameters& params) override; void Deinitialize() override; void SetBorder(float width, boost::gil::rgba8_pixel_t color); @@ -39,7 +37,9 @@ public: void SetVisible(bool visible); private: - std::unique_ptr p; + class Impl; + + std::unique_ptr p; }; } // namespace draw diff --git a/scwx-qt/source/scwx/qt/gl/gl_context.cpp b/scwx-qt/source/scwx/qt/gl/gl_context.cpp new file mode 100644 index 00000000..d0e0fd80 --- /dev/null +++ b/scwx-qt/source/scwx/qt/gl/gl_context.cpp @@ -0,0 +1,90 @@ +#include +#include +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace gl +{ + +static const std::string logPrefix_ = "scwx::qt::gl::gl_context"; + +class GlContext::Impl +{ +public: + explicit Impl() : + gl_ {}, + shaderProgramMap_ {}, + shaderProgramMutex_ {}, + textureAtlas_ {GL_INVALID_INDEX}, + textureMutex_ {} + { + } + ~Impl() {} + + gl::OpenGLFunctions gl_; + + std::unordered_map, + std::shared_ptr, + scwx::util::hash>> + shaderProgramMap_; + std::mutex shaderProgramMutex_; + + GLuint textureAtlas_; + std::mutex textureMutex_; +}; + +GlContext::GlContext() : p(std::make_unique()) {} +GlContext::~GlContext() = default; + +GlContext::GlContext(GlContext&&) noexcept = default; +GlContext& GlContext::operator=(GlContext&&) noexcept = default; + +gl::OpenGLFunctions& GlContext::gl() +{ + return p->gl_; +} + +std::shared_ptr +GlContext::GetShaderProgram(const std::string& vertexPath, + const std::string& fragmentPath) +{ + const std::pair key {vertexPath, fragmentPath}; + std::shared_ptr shaderProgram; + + std::unique_lock lock(p->shaderProgramMutex_); + + auto it = p->shaderProgramMap_.find(key); + + if (it == p->shaderProgramMap_.end()) + { + shaderProgram = std::make_shared(p->gl_); + shaderProgram->Load(vertexPath, fragmentPath); + p->shaderProgramMap_[key] = shaderProgram; + } + else + { + shaderProgram = it->second; + } + + return shaderProgram; +} + +GLuint GlContext::GetTextureAtlas() +{ + std::unique_lock lock(p->textureMutex_); + + if (p->textureAtlas_ == GL_INVALID_INDEX) + { + p->textureAtlas_ = util::TextureAtlas::Instance().BufferAtlas(p->gl_); + } + + return p->textureAtlas_; +} + +} // namespace gl +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/gl/gl_context.hpp b/scwx-qt/source/scwx/qt/gl/gl_context.hpp new file mode 100644 index 00000000..623b5855 --- /dev/null +++ b/scwx-qt/source/scwx/qt/gl/gl_context.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace gl +{ + +class GlContext +{ +public: + explicit GlContext(); + virtual ~GlContext(); + + GlContext(const GlContext&) = delete; + GlContext& operator=(const GlContext&) = delete; + + GlContext(GlContext&&) noexcept; + GlContext& operator=(GlContext&&) noexcept; + + gl::OpenGLFunctions& gl(); + + std::shared_ptr + GetShaderProgram(const std::string& vertexPath, + const std::string& fragmentPath); + + GLuint GetTextureAtlas(); + +private: + class Impl; + + std::unique_ptr p; +}; + +} // namespace gl +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/gl/shader_program.cpp b/scwx-qt/source/scwx/qt/gl/shader_program.cpp index bbf0a73b..1d9e2143 100644 --- a/scwx-qt/source/scwx/qt/gl/shader_program.cpp +++ b/scwx-qt/source/scwx/qt/gl/shader_program.cpp @@ -13,19 +13,18 @@ namespace gl static const std::string logPrefix_ = "scwx::qt::gl::shader_program"; static const auto logger_ = scwx::util::Logger::Create(logPrefix_); -static constexpr GLsizei INFO_LOG_BUF_SIZE = 512; +static constexpr GLsizei kInfoLogBufSize = 512; -class ShaderProgramImpl +class ShaderProgram::Impl { public: - explicit ShaderProgramImpl(OpenGLFunctions& gl) : - gl_(gl), id_ {GL_INVALID_INDEX} + explicit Impl(OpenGLFunctions& gl) : gl_(gl), id_ {GL_INVALID_INDEX} { // Create shader program id_ = gl_.glCreateProgram(); } - ~ShaderProgramImpl() + ~Impl() { // Delete shader program gl_.glDeleteProgram(id_); @@ -37,12 +36,12 @@ public: }; ShaderProgram::ShaderProgram(OpenGLFunctions& gl) : - p(std::make_unique(gl)) + p(std::make_unique(gl)) { } ShaderProgram::~ShaderProgram() = default; -ShaderProgram::ShaderProgram(ShaderProgram&&) noexcept = default; +ShaderProgram::ShaderProgram(ShaderProgram&&) noexcept = default; ShaderProgram& ShaderProgram::operator=(ShaderProgram&&) noexcept = default; GLuint ShaderProgram::id() const @@ -59,7 +58,7 @@ bool ShaderProgram::Load(const std::string& vertexPath, GLint glSuccess; bool success = true; - char infoLog[INFO_LOG_BUF_SIZE]; + char infoLog[kInfoLogBufSize]; GLsizei logLength; QFile vertexFile(vertexPath.c_str()); @@ -102,7 +101,7 @@ bool ShaderProgram::Load(const std::string& vertexPath, // Check for errors gl.glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &glSuccess); - gl.glGetShaderInfoLog(vertexShader, INFO_LOG_BUF_SIZE, &logLength, infoLog); + gl.glGetShaderInfoLog(vertexShader, kInfoLogBufSize, &logLength, infoLog); if (!glSuccess) { logger_->error("Vertex shader compilation failed: {}", infoLog); @@ -122,8 +121,7 @@ bool ShaderProgram::Load(const std::string& vertexPath, // Check for errors gl.glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &glSuccess); - gl.glGetShaderInfoLog( - fragmentShader, INFO_LOG_BUF_SIZE, &logLength, infoLog); + gl.glGetShaderInfoLog(fragmentShader, kInfoLogBufSize, &logLength, infoLog); if (!glSuccess) { logger_->error("Fragment shader compilation failed: {}", infoLog); @@ -142,7 +140,7 @@ bool ShaderProgram::Load(const std::string& vertexPath, // Check for errors gl.glGetProgramiv(p->id_, GL_LINK_STATUS, &glSuccess); - gl.glGetProgramInfoLog(p->id_, INFO_LOG_BUF_SIZE, &logLength, infoLog); + gl.glGetProgramInfoLog(p->id_, kInfoLogBufSize, &logLength, infoLog); if (!glSuccess) { logger_->error("Shader program link failed: {}", infoLog); diff --git a/scwx-qt/source/scwx/qt/gl/shader_program.hpp b/scwx-qt/source/scwx/qt/gl/shader_program.hpp index 386cb627..9f84053e 100644 --- a/scwx-qt/source/scwx/qt/gl/shader_program.hpp +++ b/scwx-qt/source/scwx/qt/gl/shader_program.hpp @@ -16,15 +16,13 @@ namespace qt namespace gl { -class ShaderProgramImpl; - class ShaderProgram { public: explicit ShaderProgram(OpenGLFunctions& gl); virtual ~ShaderProgram(); - ShaderProgram(const ShaderProgram&) = delete; + ShaderProgram(const ShaderProgram&) = delete; ShaderProgram& operator=(const ShaderProgram&) = delete; ShaderProgram(ShaderProgram&&) noexcept; @@ -37,7 +35,9 @@ public: void Use() const; private: - std::unique_ptr p; + class Impl; + + std::unique_ptr p; }; } // namespace gl diff --git a/scwx-qt/source/scwx/qt/gl/text_shader.cpp b/scwx-qt/source/scwx/qt/gl/text_shader.cpp index 2014da68..ec8cdcf1 100644 --- a/scwx-qt/source/scwx/qt/gl/text_shader.cpp +++ b/scwx-qt/source/scwx/qt/gl/text_shader.cpp @@ -1,4 +1,5 @@ #include +#include #include #pragma warning(push, 0) @@ -18,41 +19,46 @@ static const auto logger_ = scwx::util::Logger::Create(logPrefix_); class TextShaderImpl { public: - explicit TextShaderImpl(OpenGLFunctions& gl) : - gl_ {gl}, projectionLocation_(GL_INVALID_INDEX) + explicit TextShaderImpl(std::shared_ptr context) : + context_ {context}, + shaderProgram_ {nullptr}, + projectionLocation_(GL_INVALID_INDEX) { } ~TextShaderImpl() {} - OpenGLFunctions& gl_; + std::shared_ptr context_; + std::shared_ptr shaderProgram_; GLint projectionLocation_; }; -TextShader::TextShader(OpenGLFunctions& gl) : - ShaderProgram(gl), p(std::make_unique(gl)) +TextShader::TextShader(std::shared_ptr context) : + p(std::make_unique(context)) { } TextShader::~TextShader() = default; -TextShader::TextShader(TextShader&&) noexcept = default; +TextShader::TextShader(TextShader&&) noexcept = default; TextShader& TextShader::operator=(TextShader&&) noexcept = default; bool TextShader::Initialize() { - OpenGLFunctions& gl = p->gl_; + OpenGLFunctions& gl = p->context_->gl(); // Load and configure shader - bool success = Load(":/gl/text.vert", ":/gl/text.frag"); + p->shaderProgram_ = + p->context_->GetShaderProgram(":/gl/text.vert", ":/gl/text.frag"); - p->projectionLocation_ = gl.glGetUniformLocation(id(), "projection"); + p->projectionLocation_ = + gl.glGetUniformLocation(p->shaderProgram_->id(), "projection"); if (p->projectionLocation_ == -1) { logger_->warn("Could not find projection"); } - return success; + return true; } void TextShader::RenderText(const std::string& text, @@ -65,9 +71,9 @@ void TextShader::RenderText(const std::string& text, GLuint textureId, TextAlign align) { - OpenGLFunctions& gl = p->gl_; + OpenGLFunctions& gl = p->context_->gl(); - Use(); + p->shaderProgram_->Use(); gl.glEnable(GL_BLEND); gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); @@ -103,7 +109,7 @@ void TextShader::RenderText(const std::string& text, void TextShader::SetProjection(const glm::mat4& projection) { - p->gl_.glUniformMatrix4fv( + p->context_->gl().glUniformMatrix4fv( p->projectionLocation_, 1, GL_FALSE, glm::value_ptr(projection)); } diff --git a/scwx-qt/source/scwx/qt/gl/text_shader.hpp b/scwx-qt/source/scwx/qt/gl/text_shader.hpp index a64f6d79..ed81907a 100644 --- a/scwx-qt/source/scwx/qt/gl/text_shader.hpp +++ b/scwx-qt/source/scwx/qt/gl/text_shader.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include @@ -24,13 +24,13 @@ enum class TextAlign class TextShaderImpl; -class TextShader : public ShaderProgram +class TextShader { public: - explicit TextShader(OpenGLFunctions& gl); + explicit TextShader(std::shared_ptr context); ~TextShader(); - TextShader(const TextShader&) = delete; + TextShader(const TextShader&) = delete; TextShader& operator=(const TextShader&) = delete; TextShader(TextShader&&) noexcept; diff --git a/scwx-qt/source/scwx/qt/manager/resource_manager.cpp b/scwx-qt/source/scwx/qt/manager/resource_manager.cpp index 12d041d9..748a1194 100644 --- a/scwx-qt/source/scwx/qt/manager/resource_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/resource_manager.cpp @@ -1,5 +1,6 @@ #include #include +#include namespace scwx { @@ -20,6 +21,13 @@ static void LoadFonts() { util::Font::Create(":/res/fonts/din1451alt.ttf"); util::Font::Create(":/res/fonts/din1451alt_g.ttf"); + + util::TextureAtlas& textureAtlas = util::TextureAtlas::Instance(); + textureAtlas.RegisterTexture("lines/default-1x7", + ":/res/textures/lines/default-1x7.png"); + textureAtlas.RegisterTexture("lines/test-pattern", + ":/res/textures/lines/test-pattern.png"); + textureAtlas.BuildAtlas(8, 8); } } // namespace ResourceManager diff --git a/scwx-qt/source/scwx/qt/map/color_table_layer.cpp b/scwx-qt/source/scwx/qt/map/color_table_layer.cpp index c307c463..07512fd7 100644 --- a/scwx-qt/source/scwx/qt/map/color_table_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/color_table_layer.cpp @@ -21,7 +21,7 @@ class ColorTableLayerImpl { public: explicit ColorTableLayerImpl(std::shared_ptr context) : - shaderProgram_(context->gl_), + shaderProgram_(nullptr), uMVPMatrixLocation_(GL_INVALID_INDEX), vbo_ {GL_INVALID_INDEX}, vao_ {GL_INVALID_INDEX}, @@ -31,7 +31,8 @@ public: } ~ColorTableLayerImpl() = default; - gl::ShaderProgram shaderProgram_; + std::shared_ptr shaderProgram_; + GLint uMVPMatrixLocation_; std::array vbo_; GLuint vao_; @@ -52,13 +53,14 @@ void ColorTableLayer::Initialize() { logger_->debug("Initialize()"); - gl::OpenGLFunctions& gl = context()->gl_; + gl::OpenGLFunctions& gl = context()->gl(); // Load and configure overlay shader - p->shaderProgram_.Load(":/gl/texture1d.vert", ":/gl/texture1d.frag"); + p->shaderProgram_ = + context()->GetShaderProgram(":/gl/texture1d.vert", ":/gl/texture1d.frag"); p->uMVPMatrixLocation_ = - gl.glGetUniformLocation(p->shaderProgram_.id(), "uMVPMatrix"); + gl.glGetUniformLocation(p->shaderProgram_->id(), "uMVPMatrix"); if (p->uMVPMatrixLocation_ == -1) { logger_->warn("Could not find uMVPMatrix"); @@ -66,7 +68,7 @@ void ColorTableLayer::Initialize() gl.glGenTextures(1, &p->texture_); - p->shaderProgram_.Use(); + p->shaderProgram_->Use(); // Generate a vertex array object gl.glGenVertexArrays(1, &p->vao_); @@ -99,7 +101,7 @@ void ColorTableLayer::Initialize() gl.glVertexAttribPointer(1, 1, GL_FLOAT, GL_FALSE, 0, static_cast(0)); gl.glEnableVertexAttribArray(1); - connect(context()->radarProductView_.get(), + connect(context()->radar_product_view().get(), &view::RadarProductView::ColorTableUpdated, this, [=]() { p->colorTableNeedsUpdate_ = true; }); @@ -107,10 +109,11 @@ void ColorTableLayer::Initialize() void ColorTableLayer::Render(const QMapbox::CustomLayerRenderParameters& params) { - gl::OpenGLFunctions& gl = context()->gl_; + gl::OpenGLFunctions& gl = context()->gl(); + auto radarProductView = context()->radar_product_view(); - if (context()->radarProductView_ == nullptr || - !context()->radarProductView_->IsInitialized()) + if (context()->radar_product_view() == nullptr || + !context()->radar_product_view()->IsInitialized()) { // Defer rendering until view is initialized return; @@ -121,14 +124,14 @@ void ColorTableLayer::Render(const QMapbox::CustomLayerRenderParameters& params) 0.0f, static_cast(params.height)); - p->shaderProgram_.Use(); + p->shaderProgram_->Use(); gl.glUniformMatrix4fv( p->uMVPMatrixLocation_, 1, GL_FALSE, glm::value_ptr(projection)); if (p->colorTableNeedsUpdate_) { - p->colorTable_ = context()->radarProductView_->color_table(); + p->colorTable_ = radarProductView->color_table(); gl.glActiveTexture(GL_TEXTURE0); gl.glBindTexture(GL_TEXTURE_1D, p->texture_); @@ -145,9 +148,8 @@ void ColorTableLayer::Render(const QMapbox::CustomLayerRenderParameters& params) gl.glGenerateMipmap(GL_TEXTURE_1D); } - if (p->colorTable_.size() > 0 && - context()->radarProductView_->sweep_time() != - std::chrono::system_clock::time_point()) + if (p->colorTable_.size() > 0 && radarProductView->sweep_time() != + std::chrono::system_clock::time_point()) { // Color table panel vertices const float vertexLX = 0.0f; @@ -176,7 +178,7 @@ void ColorTableLayer::Deinitialize() { logger_->debug("Deinitialize()"); - gl::OpenGLFunctions& gl = context()->gl_; + gl::OpenGLFunctions& gl = context()->gl(); gl.glDeleteVertexArrays(1, &p->vao_); gl.glDeleteBuffers(2, p->vbo_.data()); diff --git a/scwx-qt/source/scwx/qt/map/draw_layer.cpp b/scwx-qt/source/scwx/qt/map/draw_layer.cpp index 16e1311d..47a3f6c4 100644 --- a/scwx-qt/source/scwx/qt/map/draw_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/draw_layer.cpp @@ -2,12 +2,6 @@ #include #include -#pragma warning(push, 0) -#include -#include -#include -#pragma warning(pop) - namespace scwx { namespace qt @@ -22,16 +16,14 @@ class DrawLayerImpl { public: explicit DrawLayerImpl(std::shared_ptr context) : - shaderProgram_ {context->gl_}, uMVPMatrixLocation_(GL_INVALID_INDEX) + context_ {context}, drawList_ {}, textureAtlas_ {GL_INVALID_INDEX} { } - ~DrawLayerImpl() {} - gl::ShaderProgram shaderProgram_; - GLint uMVPMatrixLocation_; - + std::shared_ptr context_; std::vector> drawList_; + GLuint textureAtlas_; }; DrawLayer::DrawLayer(std::shared_ptr context) : @@ -42,20 +34,9 @@ DrawLayer::~DrawLayer() = default; void DrawLayer::Initialize() { - gl::OpenGLFunctions& gl = context()->gl_; + p->textureAtlas_ = p->context_->GetTextureAtlas(); - p->shaderProgram_.Load(":/gl/color.vert", ":/gl/color.frag"); - - p->uMVPMatrixLocation_ = - gl.glGetUniformLocation(p->shaderProgram_.id(), "uMVPMatrix"); - if (p->uMVPMatrixLocation_ == -1) - { - logger_->warn("Could not find uMVPMatrix"); - } - - p->shaderProgram_.Use(); - - for (auto item : p->drawList_) + for (auto& item : p->drawList_) { item->Initialize(); } @@ -63,27 +44,22 @@ void DrawLayer::Initialize() void DrawLayer::Render(const QMapbox::CustomLayerRenderParameters& params) { - gl::OpenGLFunctions& gl = context()->gl_; + gl::OpenGLFunctions& gl = p->context_->gl(); - p->shaderProgram_.Use(); + gl.glActiveTexture(GL_TEXTURE0); + gl.glBindTexture(GL_TEXTURE_2D, p->textureAtlas_); - glm::mat4 projection = glm::ortho(0.0f, - static_cast(params.width), - 0.0f, - static_cast(params.height)); - - gl.glUniformMatrix4fv( - p->uMVPMatrixLocation_, 1, GL_FALSE, glm::value_ptr(projection)); - - for (auto item : p->drawList_) + for (auto& item : p->drawList_) { - item->Render(); + item->Render(params); } } void DrawLayer::Deinitialize() { - for (auto item : p->drawList_) + p->textureAtlas_ = GL_INVALID_INDEX; + + for (auto& item : p->drawList_) { item->Deinitialize(); } diff --git a/scwx-qt/source/scwx/qt/map/map_context.cpp b/scwx-qt/source/scwx/qt/map/map_context.cpp new file mode 100644 index 00000000..68b94e8e --- /dev/null +++ b/scwx-qt/source/scwx/qt/map/map_context.cpp @@ -0,0 +1,90 @@ +#include + +namespace scwx +{ +namespace qt +{ +namespace map +{ + +class MapContext::Impl +{ +public: + explicit Impl(std::shared_ptr radarProductView) : + settings_ {}, + radarProductView_ {radarProductView}, + radarProductGroup_ {common::RadarProductGroup::Unknown}, + radarProduct_ {"???"}, + radarProductCode_ {0} + { + } + + ~Impl() {} + + MapSettings settings_; + std::shared_ptr radarProductView_; + common::RadarProductGroup radarProductGroup_; + std::string radarProduct_; + int16_t radarProductCode_; +}; + +MapContext::MapContext( + std::shared_ptr radarProductView) : + p(std::make_unique(radarProductView)) +{ +} +MapContext::~MapContext() = default; + +MapContext::MapContext(MapContext&&) noexcept = default; +MapContext& MapContext::operator=(MapContext&&) noexcept = default; + +MapSettings& MapContext::settings() +{ + return p->settings_; +} + +std::shared_ptr MapContext::radar_product_view() const +{ + return p->radarProductView_; +} + +common::RadarProductGroup MapContext::radar_product_group() const +{ + return p->radarProductGroup_; +} + +std::string MapContext::radar_product() const +{ + return p->radarProduct_; +} + +int16_t MapContext::radar_product_code() const +{ + return p->radarProductCode_; +} + +void MapContext::set_radar_product_view( + std::shared_ptr radarProductView) +{ + p->radarProductView_ = radarProductView; +} + +void MapContext::set_radar_product_group( + common::RadarProductGroup radarProductGroup) +{ + p->radarProductGroup_ = radarProductGroup; +} + +void MapContext::set_radar_product(const std::string& radarProduct) +{ + p->radarProduct_ = radarProduct; +} + +void MapContext::set_radar_product_code(int16_t radarProductCode) +{ + p->radarProductCode_ = radarProductCode; +} + +} // namespace map +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/map/map_context.hpp b/scwx-qt/source/scwx/qt/map/map_context.hpp index 7a249286..47f3cc11 100644 --- a/scwx-qt/source/scwx/qt/map/map_context.hpp +++ b/scwx-qt/source/scwx/qt/map/map_context.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include @@ -11,32 +11,35 @@ namespace qt namespace map { -struct MapContext +class MapContext : public gl::GlContext { +public: explicit MapContext( - std::shared_ptr radarProductView = nullptr) : - gl_ {}, - settings_ {}, - radarProductView_ {radarProductView}, - radarProductGroup_ {common::RadarProductGroup::Unknown}, - radarProduct_ {"???"}, - radarProductCode_ {0} - { - } - ~MapContext() = default; + std::shared_ptr radarProductView = nullptr); + ~MapContext(); - MapContext(const MapContext&) = delete; + MapContext(const MapContext&) = delete; MapContext& operator=(const MapContext&) = delete; - MapContext(MapContext&&) noexcept = default; - MapContext& operator=(MapContext&&) noexcept = default; + MapContext(MapContext&&) noexcept; + MapContext& operator=(MapContext&&) noexcept; - gl::OpenGLFunctions gl_; - MapSettings settings_; - std::shared_ptr radarProductView_; - common::RadarProductGroup radarProductGroup_; - std::string radarProduct_; - int16_t radarProductCode_; + MapSettings& settings(); + std::shared_ptr radar_product_view() const; + common::RadarProductGroup radar_product_group() const; + std::string radar_product() const; + int16_t radar_product_code() const; + + void set_radar_product_view( + std::shared_ptr radarProductView); + void set_radar_product_group(common::RadarProductGroup radarProductGroup); + void set_radar_product(const std::string& radarProduct); + void set_radar_product_code(int16_t radarProductCode); + +private: + class Impl; + + std::unique_ptr p; }; } // namespace map diff --git a/scwx-qt/source/scwx/qt/map/map_widget.cpp b/scwx-qt/source/scwx/qt/map/map_widget.cpp index 1b60c240..b6f348cf 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.cpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.cpp @@ -153,9 +153,11 @@ common::Level3ProductCategoryMap MapWidget::GetAvailableLevel3Categories() float MapWidget::GetElevation() const { - if (p->context_->radarProductView_ != nullptr) + auto radarProductView = p->context_->radar_product_view(); + + if (radarProductView != nullptr) { - return p->context_->radarProductView_->elevation(); + return radarProductView->elevation(); } else { @@ -165,9 +167,11 @@ float MapWidget::GetElevation() const std::vector MapWidget::GetElevationCuts() const { - if (p->context_->radarProductView_ != nullptr) + auto radarProductView = p->context_->radar_product_view(); + + if (radarProductView != nullptr) { - return p->context_->radarProductView_->GetElevationCuts(); + return radarProductView->GetElevationCuts(); } else { @@ -182,10 +186,12 @@ MapWidgetImpl::GetLevel2ProductOrDefault(const std::string& productName) const if (level2Product == common::Level2Product::Unknown) { - if (context_->radarProductView_ != nullptr) + auto radarProductView = context_->radar_product_view(); + + if (radarProductView != nullptr) { - level2Product = common::GetLevel2Product( - context_->radarProductView_->GetRadarProductName()); + level2Product = + common::GetLevel2Product(radarProductView->GetRadarProductName()); } } @@ -218,9 +224,11 @@ std::vector MapWidget::GetLevel3Products() common::RadarProductGroup MapWidget::GetRadarProductGroup() const { - if (p->context_->radarProductView_ != nullptr) + auto radarProductView = p->context_->radar_product_view(); + + if (radarProductView != nullptr) { - return p->context_->radarProductView_->GetRadarProductGroup(); + return radarProductView->GetRadarProductGroup(); } else { @@ -230,10 +238,11 @@ common::RadarProductGroup MapWidget::GetRadarProductGroup() const std::string MapWidget::GetRadarProductName() const { + auto radarProductView = p->context_->radar_product_view(); - if (p->context_->radarProductView_ != nullptr) + if (radarProductView != nullptr) { - return p->context_->radarProductView_->GetRadarProductName(); + return radarProductView->GetRadarProductName(); } else { @@ -255,9 +264,11 @@ std::shared_ptr MapWidget::GetRadarSite() const uint16_t MapWidget::GetVcp() const { - if (p->context_->radarProductView_ != nullptr) + auto radarProductView = p->context_->radar_product_view(); + + if (radarProductView != nullptr) { - return p->context_->radarProductView_->vcp(); + return radarProductView->vcp(); } else { @@ -267,10 +278,12 @@ uint16_t MapWidget::GetVcp() const void MapWidget::SelectElevation(float elevation) { - if (p->context_->radarProductView_ != nullptr) + auto radarProductView = p->context_->radar_product_view(); + + if (radarProductView != nullptr) { - p->context_->radarProductView_->SelectElevation(elevation); - p->context_->radarProductView_->Update(); + radarProductView->SelectElevation(elevation); + radarProductView->Update(); } } @@ -280,8 +293,7 @@ void MapWidget::SelectRadarProduct(common::RadarProductGroup group, { bool radarProductViewCreated = false; - std::shared_ptr& radarProductView = - p->context_->radarProductView_; + auto radarProductView = p->context_->radar_product_view(); std::string productName {product}; @@ -304,12 +316,13 @@ void MapWidget::SelectRadarProduct(common::RadarProductGroup group, (radarProductView->GetRadarProductGroup() == common::RadarProductGroup::Level2 && radarProductView->GetRadarProductName() != productName) || - p->context_->radarProductCode_ != productCode) + p->context_->radar_product_code() != productCode) { p->RadarProductViewDisconnect(); radarProductView = view::RadarProductViewFactory::Create( group, productName, productCode, p->radarProductManager_); + p->context_->set_radar_product_view(radarProductView); p->RadarProductViewConnect(); @@ -320,9 +333,9 @@ void MapWidget::SelectRadarProduct(common::RadarProductGroup group, radarProductView->SelectProduct(productName); } - p->context_->radarProductGroup_ = group; - p->context_->radarProduct_ = productName; - p->context_->radarProductCode_ = productCode; + p->context_->set_radar_product_group(group); + p->context_->set_radar_product(productName); + p->context_->set_radar_product_code(productCode); if (radarProductView != nullptr) { @@ -372,7 +385,7 @@ void MapWidget::SelectRadarProduct( void MapWidget::SetActive(bool isActive) { - p->context_->settings_.isActive_ = isActive; + p->context_->settings().isActive_ = isActive; update(); } @@ -382,11 +395,13 @@ void MapWidget::SetAutoRefresh(bool enabled) { p->autoRefreshEnabled_ = enabled; - if (p->autoRefreshEnabled_ && p->context_->radarProductView_ != nullptr) + auto radarProductView = p->context_->radar_product_view(); + + if (p->autoRefreshEnabled_ && radarProductView != nullptr) { p->radarProductManager_->EnableRefresh( - p->context_->radarProductView_->GetRadarProductGroup(), - p->context_->radarProductView_->GetRadarProductName(), + radarProductView->GetRadarProductGroup(), + radarProductView->GetRadarProductName(), true); } } @@ -430,7 +445,9 @@ void MapWidget::AddLayers() } p->layerList_.clear(); - if (p->context_->radarProductView_ != nullptr) + auto radarProductView = p->context_->radar_product_view(); + + if (radarProductView != nullptr) { p->radarProductLayer_ = std::make_shared(p->context_); p->colorTableLayer_ = std::make_shared(p->context_); @@ -453,7 +470,7 @@ void MapWidget::AddLayers() p->AddLayer("radar", p->radarProductLayer_, before); RadarRangeLayer::Add(p->map_, - p->context_->radarProductView_->range(), + radarProductView->range(), {radarSite->latitude(), radarSite->longitude()}); p->AddLayer("colorTable", p->colorTableLayer_); } @@ -572,7 +589,7 @@ void MapWidget::initializeGL() logger_->debug("initializeGL()"); makeCurrent(); - p->context_->gl_.initializeOpenGLFunctions(); + p->context_->gl().initializeOpenGLFunctions(); p->map_.reset(new QMapboxGL(nullptr, p->settings_, size(), pixelRatio())); connect(p->map_.get(), @@ -637,9 +654,10 @@ void MapWidgetImpl::RadarProductManagerConnect() const std::string& product, std::chrono::system_clock::time_point latestTime) { - if (autoRefreshEnabled_ && context_->radarProductGroup_ == group && + if (autoRefreshEnabled_ && + context_->radar_product_group() == group && (group == common::RadarProductGroup::Level2 || - context_->radarProduct_ == product)) + context_->radar_product() == product)) { // Create file request std::shared_ptr request = @@ -698,16 +716,18 @@ void MapWidgetImpl::InitializeNewRadarProductView( util::async( [=]() { + auto radarProductView = context_->radar_product_view(); + std::string colorTableFile = manager::SettingsManager::palette_settings()->palette(colorPalette); if (!colorTableFile.empty()) { std::shared_ptr colorTable = common::ColorTable::Load(colorTableFile); - context_->radarProductView_->LoadColorTable(colorTable); + radarProductView->LoadColorTable(colorTable); } - context_->radarProductView_->Initialize(); + radarProductView->Initialize(); }); if (map_ != nullptr) @@ -718,26 +738,28 @@ void MapWidgetImpl::InitializeNewRadarProductView( void MapWidgetImpl::RadarProductViewConnect() { - if (context_->radarProductView_ != nullptr) + auto radarProductView = context_->radar_product_view(); + + if (radarProductView != nullptr) { connect( - context_->radarProductView_.get(), + radarProductView.get(), &view::RadarProductView::ColorTableUpdated, this, [&]() { widget_->update(); }, Qt::QueuedConnection); connect( - context_->radarProductView_.get(), + radarProductView.get(), &view::RadarProductView::SweepComputed, this, - [&]() + [=]() { std::shared_ptr radarSite = radarProductManager_->radar_site(); RadarRangeLayer::Update( map_, - context_->radarProductView_->range(), + radarProductView->range(), {radarSite->latitude(), radarSite->longitude()}); widget_->update(); emit widget_->RadarSweepUpdated(); @@ -748,13 +770,15 @@ void MapWidgetImpl::RadarProductViewConnect() void MapWidgetImpl::RadarProductViewDisconnect() { - if (context_->radarProductView_ != nullptr) + auto radarProductView = context_->radar_product_view(); + + if (radarProductView != nullptr) { - disconnect(context_->radarProductView_.get(), + disconnect(radarProductView.get(), &view::RadarProductView::ColorTableUpdated, this, nullptr); - disconnect(context_->radarProductView_.get(), + disconnect(radarProductView.get(), &view::RadarProductView::SweepComputed, this, nullptr); diff --git a/scwx-qt/source/scwx/qt/map/overlay_layer.cpp b/scwx-qt/source/scwx/qt/map/overlay_layer.cpp index 91ef54ef..a6e136fa 100644 --- a/scwx-qt/source/scwx/qt/map/overlay_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/overlay_layer.cpp @@ -34,12 +34,12 @@ class OverlayLayerImpl { public: explicit OverlayLayerImpl(std::shared_ptr context) : - textShader_(context->gl_), + textShader_(context), font_(util::Font::Create(":/res/fonts/din1451alt.ttf")), texture_ {GL_INVALID_INDEX}, - activeBoxOuter_ {std::make_shared(context->gl_)}, - activeBoxInner_ {std::make_shared(context->gl_)}, - timeBox_ {std::make_shared(context->gl_)}, + activeBoxOuter_ {std::make_shared(context)}, + activeBoxInner_ {std::make_shared(context)}, + timeBox_ {std::make_shared(context)}, sweepTimeString_ {}, sweepTimeNeedsUpdate_ {true} { @@ -81,7 +81,8 @@ void OverlayLayer::Initialize() DrawLayer::Initialize(); - gl::OpenGLFunctions& gl = context()->gl_; + gl::OpenGLFunctions& gl = context()->gl(); + auto radarProductView = context()->radar_product_view(); p->textShader_.Initialize(); @@ -90,9 +91,9 @@ void OverlayLayer::Initialize() p->texture_ = p->font_->GenerateTexture(gl); } - if (context()->radarProductView_ != nullptr) + if (radarProductView != nullptr) { - connect(context()->radarProductView_.get(), + connect(radarProductView.get(), &view::RadarProductView::SweepComputed, this, &OverlayLayer::UpdateSweepTimeNextFrame); @@ -103,14 +104,14 @@ void OverlayLayer::Render(const QMapbox::CustomLayerRenderParameters& params) { constexpr float fontSize = 16.0f; - gl::OpenGLFunctions& gl = context()->gl_; + gl::OpenGLFunctions& gl = context()->gl(); + auto radarProductView = context()->radar_product_view(); + auto& settings = context()->settings(); - if (p->sweepTimeNeedsUpdate_ && context()->radarProductView_ != nullptr) + if (p->sweepTimeNeedsUpdate_ && radarProductView != nullptr) { - p->sweepTimeString_ = - scwx::util::TimeString(context()->radarProductView_->sweep_time(), - std::chrono::current_zone(), - false); + p->sweepTimeString_ = scwx::util::TimeString( + radarProductView->sweep_time(), std::chrono::current_zone(), false); p->sweepTimeNeedsUpdate_ = false; } @@ -120,9 +121,9 @@ void OverlayLayer::Render(const QMapbox::CustomLayerRenderParameters& params) static_cast(params.height)); // Active Box - p->activeBoxOuter_->SetVisible(context()->settings_.isActive_); - p->activeBoxInner_->SetVisible(context()->settings_.isActive_); - if (context()->settings_.isActive_) + p->activeBoxOuter_->SetVisible(settings.isActive_); + p->activeBoxInner_->SetVisible(settings.isActive_); + if (settings.isActive_) { p->activeBoxOuter_->SetSize(params.width, params.height); p->activeBoxInner_->SetSize(params.width - 2.0f, params.height - 2.0f); @@ -164,15 +165,16 @@ void OverlayLayer::Deinitialize() DrawLayer::Deinitialize(); - gl::OpenGLFunctions& gl = context()->gl_; + gl::OpenGLFunctions& gl = context()->gl(); + auto radarProductView = context()->radar_product_view(); gl.glDeleteTextures(1, &p->texture_); p->texture_ = GL_INVALID_INDEX; - if (context()->radarProductView_ != nullptr) + if (radarProductView != nullptr) { - disconnect(context()->radarProductView_.get(), + disconnect(radarProductView.get(), &view::RadarProductView::SweepComputed, this, &OverlayLayer::UpdateSweepTimeNextFrame); diff --git a/scwx-qt/source/scwx/qt/map/radar_product_layer.cpp b/scwx-qt/source/scwx/qt/map/radar_product_layer.cpp index 2f47b777..c7b501ac 100644 --- a/scwx-qt/source/scwx/qt/map/radar_product_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/radar_product_layer.cpp @@ -33,7 +33,7 @@ class RadarProductLayerImpl { public: explicit RadarProductLayerImpl(std::shared_ptr context) : - shaderProgram_(context->gl_), + shaderProgram_(nullptr), uMVPMatrixLocation_(GL_INVALID_INDEX), uMapScreenCoordLocation_(GL_INVALID_INDEX), uDataMomentOffsetLocation_(GL_INVALID_INDEX), @@ -50,7 +50,8 @@ public: } ~RadarProductLayerImpl() = default; - gl::ShaderProgram shaderProgram_; + std::shared_ptr shaderProgram_; + GLint uMVPMatrixLocation_; GLint uMapScreenCoordLocation_; GLint uDataMomentOffsetLocation_; @@ -78,47 +79,48 @@ void RadarProductLayer::Initialize() { logger_->debug("Initialize()"); - gl::OpenGLFunctions& gl = context()->gl_; + gl::OpenGLFunctions& gl = context()->gl(); // Load and configure radar shader - p->shaderProgram_.Load(":/gl/radar.vert", ":/gl/radar.frag"); + p->shaderProgram_ = + context()->GetShaderProgram(":/gl/radar.vert", ":/gl/radar.frag"); p->uMVPMatrixLocation_ = - gl.glGetUniformLocation(p->shaderProgram_.id(), "uMVPMatrix"); + gl.glGetUniformLocation(p->shaderProgram_->id(), "uMVPMatrix"); if (p->uMVPMatrixLocation_ == -1) { logger_->warn("Could not find uMVPMatrix"); } p->uMapScreenCoordLocation_ = - gl.glGetUniformLocation(p->shaderProgram_.id(), "uMapScreenCoord"); + gl.glGetUniformLocation(p->shaderProgram_->id(), "uMapScreenCoord"); if (p->uMapScreenCoordLocation_ == -1) { logger_->warn("Could not find uMapScreenCoord"); } p->uDataMomentOffsetLocation_ = - gl.glGetUniformLocation(p->shaderProgram_.id(), "uDataMomentOffset"); + gl.glGetUniformLocation(p->shaderProgram_->id(), "uDataMomentOffset"); if (p->uDataMomentOffsetLocation_ == -1) { logger_->warn("Could not find uDataMomentOffset"); } p->uDataMomentScaleLocation_ = - gl.glGetUniformLocation(p->shaderProgram_.id(), "uDataMomentScale"); + gl.glGetUniformLocation(p->shaderProgram_->id(), "uDataMomentScale"); if (p->uDataMomentScaleLocation_ == -1) { logger_->warn("Could not find uDataMomentScale"); } p->uCFPEnabledLocation_ = - gl.glGetUniformLocation(p->shaderProgram_.id(), "uCFPEnabled"); + gl.glGetUniformLocation(p->shaderProgram_->id(), "uCFPEnabled"); if (p->uCFPEnabledLocation_ == -1) { logger_->warn("Could not find uCFPEnabled"); } - p->shaderProgram_.Use(); + p->shaderProgram_->Use(); // Generate a vertex array object gl.glGenVertexArrays(1, &p->vao_); @@ -138,11 +140,12 @@ void RadarProductLayer::Initialize() gl.glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); gl.glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - connect(context()->radarProductView_.get(), + auto radarProductView = context()->radar_product_view(); + connect(radarProductView.get(), &view::RadarProductView::ColorTableUpdated, this, [=]() { p->colorTableNeedsUpdate_ = true; }); - connect(context()->radarProductView_.get(), + connect(radarProductView.get(), &view::RadarProductView::SweepComputed, this, [=]() { p->sweepNeedsUpdate_ = true; }); @@ -152,12 +155,12 @@ void RadarProductLayer::UpdateSweep() { logger_->debug("UpdateSweep()"); - gl::OpenGLFunctions& gl = context()->gl_; + gl::OpenGLFunctions& gl = context()->gl(); boost::timer::cpu_timer timer; std::shared_ptr radarProductView = - context()->radarProductView_; + context()->radar_product_view(); std::unique_lock sweepLock(radarProductView->sweep_mutex(), std::try_to_lock); @@ -253,9 +256,9 @@ void RadarProductLayer::UpdateSweep() void RadarProductLayer::Render( const QMapbox::CustomLayerRenderParameters& params) { - gl::OpenGLFunctions& gl = context()->gl_; + gl::OpenGLFunctions& gl = context()->gl(); - p->shaderProgram_.Use(); + p->shaderProgram_->Use(); if (p->colorTableNeedsUpdate_) { @@ -300,7 +303,7 @@ void RadarProductLayer::Deinitialize() { logger_->debug("Deinitialize()"); - gl::OpenGLFunctions& gl = context()->gl_; + gl::OpenGLFunctions& gl = context()->gl(); gl.glDeleteVertexArrays(1, &p->vao_); gl.glDeleteBuffers(3, p->vbo_.data()); @@ -321,9 +324,9 @@ void RadarProductLayer::UpdateColorTable() p->colorTableNeedsUpdate_ = false; - gl::OpenGLFunctions& gl = context()->gl_; + gl::OpenGLFunctions& gl = context()->gl(); std::shared_ptr radarProductView = - context()->radarProductView_; + context()->radar_product_view(); const std::vector& colorTable = radarProductView->color_table(); diff --git a/scwx-qt/source/scwx/qt/util/streams.hpp b/scwx-qt/source/scwx/qt/util/streams.hpp new file mode 100644 index 00000000..ca3a3d9c --- /dev/null +++ b/scwx-qt/source/scwx/qt/util/streams.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace util +{ + +class IoDeviceSource +{ +public: + typedef char char_type; + typedef boost::iostreams::source_tag category; + + IoDeviceSource(QIODevice& source) : source_ {source} {} + ~IoDeviceSource() {} + + std::streamsize read(char* buffer, std::streamsize n) + { + return source_.read(buffer, n); + } + +private: + QIODevice& source_; +}; + +} // namespace util +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/util/texture_atlas.cpp b/scwx-qt/source/scwx/qt/util/texture_atlas.cpp new file mode 100644 index 00000000..22a7f0f7 --- /dev/null +++ b/scwx-qt/source/scwx/qt/util/texture_atlas.cpp @@ -0,0 +1,292 @@ +#include +#include +#include + +#include +#include + +#pragma warning(push, 0) +#pragma warning(disable : 4714) +#include +#include +#include +#include +#pragma warning(pop) + +namespace scwx +{ +namespace qt +{ +namespace util +{ + +static const std::string logPrefix_ = "scwx::qt::util::texture_atlas"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); + +class TextureAtlas::Impl +{ +public: + explicit Impl() : + texturePathMap_ {}, + texturePathMutex_ {}, + atlas_ {}, + atlasMap_ {}, + atlasMutex_ {} + { + } + ~Impl() {} + + static boost::gil::rgba8_image_t LoadImage(const std::string& imagePath); + + std::unordered_map texturePathMap_; + std::shared_mutex texturePathMutex_; + + boost::gil::rgba8_image_t atlas_; + std::unordered_map atlasMap_; + std::shared_mutex atlasMutex_; +}; + +TextureAtlas::TextureAtlas() : p(std::make_unique()) {} +TextureAtlas::~TextureAtlas() = default; + +TextureAtlas::TextureAtlas(TextureAtlas&&) noexcept = default; +TextureAtlas& TextureAtlas::operator=(TextureAtlas&&) noexcept = default; + +void TextureAtlas::RegisterTexture(const std::string& name, + const std::string& path) +{ + std::unique_lock lock(p->texturePathMutex_); + p->texturePathMap_.insert_or_assign(name, path); +} + +void TextureAtlas::BuildAtlas(size_t width, size_t height) +{ + logger_->debug("Building {}x{} texture atlas", width, height); + + if (width > INT_MAX || height > INT_MAX) + { + logger_->error("Cannot build texture atlas of size {}x{}", width, height); + return; + } + + std::vector> images; + std::vector stbrpRects; + + // Load images + { + // Take a read lock on the texture path map + std::shared_lock lock(p->texturePathMutex_); + + // For each registered texture + std::for_each(p->texturePathMap_.cbegin(), + p->texturePathMap_.cend(), + [&](const auto& pair) + { + // Load texture image + boost::gil::rgba8_image_t image = + Impl::LoadImage(pair.second); + + if (image.width() > 0u && image.height() > 0u) + { + // Store STB rectangle pack data in a vector + stbrpRects.push_back(stbrp_rect { + 0, + static_cast(image.width()), + static_cast(image.height()), + 0, + 0, + 0}); + + // Store image data in a vector + images.emplace_back(pair.first, std::move(image)); + } + }); + } + + // Pack images + { + logger_->trace("Packing {} images", images.size()); + + // Optimal number of nodes = width + stbrp_context stbrpContext; + std::vector stbrpNodes(width); + + stbrp_init_target(&stbrpContext, + static_cast(width), + static_cast(height), + stbrpNodes.data(), + static_cast(stbrpNodes.size())); + + // Pack loaded textures + stbrp_pack_rects( + &stbrpContext, stbrpRects.data(), static_cast(stbrpRects.size())); + } + + // Lock atlas + std::unique_lock lock(p->atlasMutex_); + + // Clear index + p->atlasMap_.clear(); + + // Clear atlas + p->atlas_.recreate(width, height); + boost::gil::rgba8_view_t atlasView = boost::gil::view(p->atlas_); + boost::gil::fill_pixels(atlasView, + boost::gil::rgba8_pixel_t {255, 0, 255, 255}); + + // Populate atlas + logger_->trace("Populating atlas"); + + const float xStep = 1.0f / width; + const float yStep = 1.0f / height; + const float xMin = xStep * 0.5f; + const float yMin = yStep * 0.5f; + + for (size_t i = 0; i < images.size(); i++) + { + // If the image was packed successfully + if (stbrpRects[i].was_packed != 0) + { + // Populate the atlas + boost::gil::rgba8c_view_t imageView = + boost::gil::const_view(images[i].second); + boost::gil::rgba8_view_t atlasSubView = + boost::gil::subimage_view(atlasView, + stbrpRects[i].x, + stbrpRects[i].y, + imageView.width(), + imageView.height()); + + boost::gil::copy_pixels(imageView, atlasSubView); + + // Add texture image to the index + const stbrp_coord x = stbrpRects[i].x; + const stbrp_coord y = stbrpRects[i].y; + + const float sLeft = x * xStep + xMin; + const float sRight = + sLeft + static_cast(imageView.width() - 1) / width; + const float tTop = y * yStep + yMin; + const float tBottom = + tTop + static_cast(imageView.height() - 1) / height; + + p->atlasMap_.emplace( + std::piecewise_construct, + std::forward_as_tuple(images[i].first), + std::forward_as_tuple( + boost::gil::point_t {x, y}, + boost::gil::point_t {imageView.width(), imageView.height()}, + sLeft, + sRight, + tTop, + tBottom)); + } + else + { + logger_->warn("Unable to pack texture: {}", images[i].first); + } + } +} + +GLuint TextureAtlas::BufferAtlas(gl::OpenGLFunctions& gl) +{ + GLuint texture = GL_INVALID_INDEX; + + std::shared_lock lock(p->atlasMutex_); + + if (p->atlas_.width() > 0u && p->atlas_.height() > 0u) + { + boost::gil::rgba8_view_t view = boost::gil::view(p->atlas_); + std::vector pixelData(view.width() * + view.height()); + + boost::gil::copy_pixels( + view, + boost::gil::interleaved_view(view.width(), + view.height(), + pixelData.data(), + view.width() * + sizeof(boost::gil::rgba8_pixel_t))); + + lock.unlock(); + + gl.glGenTextures(1, &texture); + gl.glBindTexture(GL_TEXTURE_2D, texture); + + gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + gl.glTexImage2D(GL_TEXTURE_2D, + 0, + GL_RGBA, + view.width(), + view.height(), + 0, + GL_RGBA, + GL_UNSIGNED_BYTE, + pixelData.data()); + } + + return texture; +} + +TextureAttributes TextureAtlas::GetTextureAttributes(const std::string& name) +{ + TextureAttributes attr {}; + std::shared_lock lock(p->atlasMutex_); + + const auto& it = p->atlasMap_.find(name); + if (it != p->atlasMap_.cend()) + { + attr = it->second; + } + + return attr; +} + +boost::gil::rgba8_image_t +TextureAtlas::Impl::LoadImage(const std::string& imagePath) +{ + logger_->debug("Loading image: {}", imagePath); + + boost::gil::rgba8_image_t image; + + QFile imageFile(imagePath.c_str()); + + imageFile.open(QIODevice::ReadOnly); + + if (!imageFile.isOpen()) + { + logger_->error("Could not open image: {}", imagePath); + return std::move(image); + } + + boost::iostreams::stream dataStream(imageFile); + + boost::gil::image x; + + try + { + boost::gil::read_and_convert_image( + dataStream, image, boost::gil::png_tag()); + } + catch (const std::exception& ex) + { + logger_->error("Error reading image: {}", ex.what()); + return std::move(image); + } + + return std::move(image); +} + +TextureAtlas& TextureAtlas::Instance() +{ + static TextureAtlas instance_ {}; + return instance_; +} + +} // namespace util +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/util/texture_atlas.hpp b/scwx-qt/source/scwx/qt/util/texture_atlas.hpp new file mode 100644 index 00000000..bf904e6c --- /dev/null +++ b/scwx-qt/source/scwx/qt/util/texture_atlas.hpp @@ -0,0 +1,83 @@ +#pragma once + +#include + +#include +#include + +#include + +namespace scwx +{ +namespace qt +{ +namespace util +{ + +struct TextureAttributes +{ + TextureAttributes() : + valid_ {false}, + position_ {}, + size_ {}, + sLeft_ {}, + sRight_ {}, + tTop_ {}, + tBottom_ {} + { + } + + TextureAttributes(boost::gil::point_t position, + boost::gil::point_t size, + float sLeft, + float sRight, + float tTop, + float tBottom) : + valid_ {true}, + position_ {position}, + size_ {size}, + sLeft_ {sLeft}, + sRight_ {sRight}, + tTop_ {tTop}, + tBottom_ {tBottom} + { + } + + bool valid_; + boost::gil::point_t position_; + boost::gil::point_t size_; + float sLeft_; + float sRight_; + float tTop_; + float tBottom_; +}; + +class TextureAtlas +{ +public: + explicit TextureAtlas(); + ~TextureAtlas(); + + TextureAtlas(const TextureAtlas&) = delete; + TextureAtlas& operator=(const TextureAtlas&) = delete; + + TextureAtlas(TextureAtlas&&) noexcept; + TextureAtlas& operator=(TextureAtlas&&) noexcept; + + static TextureAtlas& Instance(); + + void RegisterTexture(const std::string& name, const std::string& path); + void BuildAtlas(size_t width, size_t height); + GLuint BufferAtlas(gl::OpenGLFunctions& gl); + + TextureAttributes GetTextureAttributes(const std::string& name); + +private: + class Impl; + + std::unique_ptr p; +}; + +} // namespace util +} // namespace qt +} // namespace scwx diff --git a/wxdata/include/scwx/common/geographic.hpp b/wxdata/include/scwx/common/geographic.hpp index 639114f0..66a9233f 100644 --- a/wxdata/include/scwx/common/geographic.hpp +++ b/wxdata/include/scwx/common/geographic.hpp @@ -13,6 +13,8 @@ struct Coordinate double latitude_; ///< Latitude in degrees double longitude_; ///< Longitude in degrees + Coordinate() : Coordinate(0.0, 0.0) {} + Coordinate(double latitude, double longitude) : latitude_ {latitude}, longitude_ {longitude} { diff --git a/wxdata/include/scwx/util/hash.hpp b/wxdata/include/scwx/util/hash.hpp new file mode 100644 index 00000000..6559b6a4 --- /dev/null +++ b/wxdata/include/scwx/util/hash.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +namespace scwx +{ +namespace util +{ + +template +struct hash; + +template<> +struct hash> +{ + size_t operator()(const std::pair& x) const; +}; + +} // namespace util +} // namespace scwx diff --git a/wxdata/source/scwx/util/hash.cpp b/wxdata/source/scwx/util/hash.cpp new file mode 100644 index 00000000..31d928a4 --- /dev/null +++ b/wxdata/source/scwx/util/hash.cpp @@ -0,0 +1,20 @@ +#include + +#include + +namespace scwx +{ +namespace util +{ + +size_t hash>::operator()( + const std::pair& x) const +{ + size_t seed = 0; + boost::hash_combine(seed, x.first); + boost::hash_combine(seed, x.second); + return seed; +} + +} // namespace util +} // namespace scwx diff --git a/wxdata/wxdata.cmake b/wxdata/wxdata.cmake index 4adbc60d..de57a39b 100644 --- a/wxdata/wxdata.cmake +++ b/wxdata/wxdata.cmake @@ -46,6 +46,7 @@ set(SRC_PROVIDER source/scwx/provider/aws_level2_data_provider.cpp source/scwx/provider/nexrad_data_provider_factory.cpp) set(HDR_UTIL include/scwx/util/environment.hpp include/scwx/util/float.hpp + include/scwx/util/hash.hpp include/scwx/util/iterator.hpp include/scwx/util/logger.hpp include/scwx/util/map.hpp @@ -56,6 +57,7 @@ set(HDR_UTIL include/scwx/util/environment.hpp include/scwx/util/vectorbuf.hpp) set(SRC_UTIL source/scwx/util/environment.cpp source/scwx/util/float.cpp + source/scwx/util/hash.cpp source/scwx/util/logger.cpp source/scwx/util/rangebuf.cpp source/scwx/util/streams.cpp