diff --git a/src/display/MapBatches.h b/src/display/MapBatches.h index 86e4c42c1..e00454165 100644 --- a/src/display/MapBatches.h +++ b/src/display/MapBatches.h @@ -29,19 +29,17 @@ struct NODISCARD LayerMeshes final DEFAULT_MOVES_DELETE_COPIES(LayerMeshes); ~LayerMeshes() = default; - void render(int thisLayer, int focusedLayer); + void render(int thisLayer, int focusedLayer, GLuint named_colors_buffer_id); explicit operator bool() const { return isValid; } }; -using PlainQuadBatch = std::vector; - struct NODISCARD LayerMeshesIntermediate final { using Fn = std::function; using FnVec = std::vector; FnVec terrain; FnVec trails; - RoomTintArray tints; + RoomTintArray tints; FnVec overlays; FnVec doors; FnVec walls; @@ -49,7 +47,7 @@ struct NODISCARD LayerMeshesIntermediate final FnVec upDownExits; FnVec streamIns; FnVec streamOuts; - PlainQuadBatch layerBoost; + Fn layerBoost; bool isValid = false; LayerMeshesIntermediate() = default; diff --git a/src/display/MapCanvasRoomDrawer.cpp b/src/display/MapCanvasRoomDrawer.cpp index b82bb6569..f5d0214fa 100644 --- a/src/display/MapCanvasRoomDrawer.cpp +++ b/src/display/MapCanvasRoomDrawer.cpp @@ -27,6 +27,7 @@ #include "../opengl/FontFormatFlags.h" #include "../opengl/OpenGL.h" #include "../opengl/OpenGLTypes.h" +#include "../opengl/legacy/VBO.h" #include "ConnectionLineBuilder.h" #include "MapCanvasData.h" #include "RoadIndex.h" @@ -75,17 +76,21 @@ NODISCARD static VisitRoomOptions getVisitRoomOptions() return result; } -NODISCARD static bool isTransparent(const NamedColorEnum namedColor) +NODISCARD static NamedColorEnum getTintColor(const RoomTintEnum tint) { - return namedColor == NamedColorEnum::TRANSPARENT; + static_assert(NUM_ROOM_TINTS == 2); + switch (tint) { + case RoomTintEnum::DARK: + return NamedColorEnum::ROOM_DARK; + case RoomTintEnum::NO_SUNDEATH: + return NamedColorEnum::ROOM_NO_SUNDEATH; + } + std::abort(); } -NODISCARD static std::optional getColor(const NamedColorEnum namedColor) +NODISCARD static bool isTransparent(const NamedColorEnum namedColor) { - if (isTransparent(namedColor)) { - return std::nullopt; - } - return XNamedColor{namedColor}.getColor(); + return namedColor == NamedColorEnum::TRANSPARENT; } enum class NODISCARD WallOrientationEnum { HORIZONTAL, VERTICAL }; @@ -404,7 +409,18 @@ static void visitRooms(const RoomVector &rooms, } } -struct NODISCARD RoomTex +struct NODISCARD RoomTint final +{ + Coordinate coord; + NamedColorEnum color = NamedColorEnum::DEFAULT; + + explicit RoomTint(const Coordinate input_coord, const NamedColorEnum input_color) + : coord{input_coord} + , color{input_color} + {} +}; + +struct NODISCARD RoomTex final { Coordinate coord; MMTexArrayPosition pos; @@ -424,18 +440,24 @@ struct NODISCARD RoomTex } }; -struct NODISCARD ColoredRoomTex : public RoomTex +struct NODISCARD ColoredRoomTex final { + Coordinate coord; + MMTexArrayPosition pos; NamedColorEnum colorId = NamedColorEnum::DEFAULT; explicit ColoredRoomTex(const Coordinate input_coord, const MMTexArrayPosition input_pos, const NamedColorEnum input_color) - : RoomTex{input_coord, input_pos} + : coord{input_coord} + , pos{input_pos} , colorId{input_color} {} }; +struct NODISCARD RoomTintVector final : public std::vector +{}; + // Caution: Although O(n) partitioning into an array indexed by constant number of texture IDs // is theoretically faster than O(n log n) sorting, one naive attempt to prematurely optimize // this code resulted in a 50x slow-down. @@ -502,11 +524,14 @@ struct NODISCARD ColoredRoomTexVector final : public std::vector template static void foreach_texture(const T &textures, Callback &&callback) { + using U = utils::remove_cvref_t; + static_assert(std::is_same_v or std::is_same_v); + assert(textures.isSorted()); const auto size = textures.size(); for (size_t beg = 0, next = size; beg < size; beg = next) { - const RoomTex &rtex = textures[beg]; + const auto &rtex = textures[beg]; const auto textureId = rtex.pos.array; size_t end = beg + 1; @@ -522,6 +547,41 @@ static void foreach_texture(const T &textures, Callback &&callback) } } +NODISCARD static LayerMeshesIntermediate::Fn createMeshFn(MAYBE_UNUSED const std::string_view what, + const RoomTintVector &room_tints, + const MMTexArrayPosition texture) +{ + if (room_tints.empty()) { + return {}; + } + + const size_t count = room_tints.size(); + std::vector verts; + verts.reserve(count); + + for (auto &thisVert : room_tints) { + const auto &pos = thisVert.coord; + const auto v0 = pos.to_ivec3(); + verts.emplace_back(v0, texture.position, thisVert.color); + } + + return [v = std::move(verts), texture](OpenGL &g) { + return g.createRoomQuadTexBatch(v, texture.array); + }; +} + +NODISCARD static RoomTintArray createMeshFns( + MAYBE_UNUSED const std::string_view what, + const RoomTintArray &room_tints, + const MMTexArrayPosition texture) +{ + RoomTintArray result; + for (auto tint : ALL_ROOM_TINTS) { + result[tint] = createMeshFn(what, room_tints[tint], texture); + } + return result; +} + NODISCARD static LayerMeshesIntermediate::FnVec createSortedTexturedMeshes( const std::string_view what, const RoomTexVector &textures) { @@ -555,28 +615,19 @@ NODISCARD static LayerMeshesIntermediate::FnVec createSortedTexturedMeshes( const RoomTex &rtex = textures[beg]; const size_t count = end - beg; - std::vector verts; - verts.reserve(count * VERTS_PER_QUAD); /* quads */ + std::vector verts; + verts.reserve(count); - // D-C - // | | ccw winding - // A-B for (size_t i = beg; i < end; ++i) { const RoomTex &thisVert = textures[i]; const auto &pos = thisVert.coord; - const auto v0 = pos.to_vec3(); + const auto v0 = pos.to_ivec3(); const auto z = thisVert.pos.position; - -#define EMIT(x, y) verts.emplace_back(glm::vec3((x), (y), z), v0 + glm::vec3((x), (y), 0)) - EMIT(0, 0); - EMIT(1, 0); - EMIT(1, 1); - EMIT(0, 1); -#undef EMIT + verts.emplace_back(v0, z); } tmp_meshes.emplace_back([v = std::move(verts), t = rtex.pos.array](OpenGL &g) { - return g.createTexturedQuadBatch(v, t); + return g.createRoomQuadTexBatch(v, t); }); }; @@ -616,32 +667,23 @@ NODISCARD static LayerMeshesIntermediate::FnVec createSortedColoredTexturedMeshe tmp_meshes.reserve(numUniqueTextures); const auto lambda = [&tmp_meshes, &textures](const size_t beg, const size_t end) -> void { - const RoomTex &rtex = textures[beg]; + const ColoredRoomTex &rtex = textures[beg]; const size_t count = end - beg; - std::vector verts; - verts.reserve(count * VERTS_PER_QUAD); /* quads */ + std::vector verts; + verts.reserve(count); - // D-C - // | | ccw winding - // A-B for (size_t i = beg; i < end; ++i) { const ColoredRoomTex &thisVert = textures[i]; const auto &pos = thisVert.coord; - const auto v0 = pos.to_vec3(); - const auto color = XNamedColor{thisVert.colorId}.getColor(); + const auto v0 = pos.to_ivec3(); + const auto color = thisVert.colorId; const auto z = thisVert.pos.position; - -#define EMIT(x, y) verts.emplace_back(color, glm::vec3((x), (y), z), v0 + glm::vec3((x), (y), 0)) - EMIT(0, 0); - EMIT(1, 0); - EMIT(1, 1); - EMIT(0, 1); -#undef EMIT + verts.emplace_back(v0, z, color); } tmp_meshes.emplace_back([v = std::move(verts), t = rtex.pos.array](OpenGL &g) { - return g.createColoredTexturedQuadBatch(v, t); + return g.createRoomQuadTexBatch(v, t); }); }; @@ -652,6 +694,8 @@ NODISCARD static LayerMeshesIntermediate::FnVec createSortedColoredTexturedMeshe struct NODISCARD LayerBatchData final { + MMTexArrayPosition white_pixel; + RoomTexVector roomTerrains; RoomTexVector roomTrails; RoomTexVector roomOverlays = RoomTexVector::sortedRoomTexVector(); @@ -663,11 +707,12 @@ struct NODISCARD LayerBatchData final ColoredRoomTexVector roomUpDownExits; RoomTexVector streamIns; RoomTexVector streamOuts; - RoomTintArray roomTints; - PlainQuadBatch roomLayerBoostQuads; + RoomTintArray roomTints; + RoomTintVector roomLayerBoostQuads; - // So it can be used in std containers - explicit LayerBatchData() = default; + explicit LayerBatchData(MMTexArrayPosition white_pixel_) + : white_pixel{white_pixel_} + {} ~LayerBatchData() = default; DEFAULT_MOVES_DELETE_COPIES(LayerBatchData); @@ -685,7 +730,7 @@ struct NODISCARD LayerBatchData final LayerMeshesIntermediate meshes; meshes.terrain = ::createSortedTexturedMeshes("terrain", roomTerrains); meshes.trails = ::createSortedTexturedMeshes("trails", roomTrails); - meshes.tints = roomTints; // REVISIT: this is a copy instead of a move + meshes.tints = ::createMeshFns("tints", roomTints, white_pixel); meshes.overlays = ::createSortedTexturedMeshes("overlays", roomOverlays); meshes.doors = ::createSortedColoredTexturedMeshes("doors", doors); meshes.walls = ::createSortedColoredTexturedMeshes("solidWalls", solidWallLines); @@ -693,7 +738,7 @@ struct NODISCARD LayerBatchData final meshes.upDownExits = ::createSortedColoredTexturedMeshes("upDownExits", roomUpDownExits); meshes.streamIns = ::createSortedTexturedMeshes("streamIns", streamIns); meshes.streamOuts = ::createSortedTexturedMeshes("streamOuts", streamOuts); - meshes.layerBoost = roomLayerBoostQuads; // REVISIT: this is a copy instead of a move + meshes.layerBoost = ::createMeshFn("layerBoost", roomLayerBoostQuads, white_pixel); meshes.isValid = true; return meshes; } @@ -739,14 +784,7 @@ class NODISCARD LayerBatchBuilder final : public IRoomVisitorCallbacks const auto pos = room.getPosition(); m_data.roomTerrains.emplace_back(pos, terrain); - - const auto v0 = pos.to_vec3(); -#define EMIT(x, y) m_data.roomLayerBoostQuads.emplace_back(v0 + glm::vec3((x), (y), 0)) - EMIT(0, 0); - EMIT(1, 0); - EMIT(1, 1); - EMIT(0, 1); -#undef EMIT + m_data.roomLayerBoostQuads.emplace_back(pos, NamedColorEnum::DEFAULT); } void virt_visitTrailTexture(const RoomHandle &room, const MMTexArrayPosition &trail) final @@ -765,13 +803,8 @@ class NODISCARD LayerBatchBuilder final : public IRoomVisitorCallbacks void virt_visitNamedColorTint(const RoomHandle &room, const RoomTintEnum tint) final { - const auto v0 = room.getPosition().to_vec3(); -#define EMIT(x, y) m_data.roomTints[tint].emplace_back(v0 + glm::vec3((x), (y), 0)) - EMIT(0, 0); - EMIT(1, 0); - EMIT(1, 1); - EMIT(0, 1); -#undef EMIT + const auto pos = room.getPosition(); + m_data.roomTints[tint].emplace_back(pos, getTintColor(tint)); } void virt_visitWall(const RoomHandle &room, @@ -842,7 +875,7 @@ NODISCARD static LayerMeshesIntermediate generateLayerMeshes( { DECL_TIMER(t, "generateLayerMeshes"); - LayerBatchData data; + LayerBatchData data{textures.white_pixel}; LayerBatchBuilder builder{data, textures, bounds}; visitRooms(rooms, textures, builder, visitRoomOptions); @@ -924,6 +957,14 @@ LayerMeshes LayerMeshesIntermediate::getLayerMeshes(OpenGL &gl) const struct NODISCARD Resolver final { OpenGL &m_gl; + + NODISCARD UniqueMesh resolve(const Fn &fn) + { + if (!fn) { + return {}; + } + return fn(m_gl); + } NODISCARD UniqueMeshVector resolve(const FnVec &v) { std::vector result; @@ -933,16 +974,12 @@ LayerMeshes LayerMeshesIntermediate::getLayerMeshes(OpenGL &gl) const } return UniqueMeshVector{std::move(result)}; } - NODISCARD UniqueMesh resolve(const PlainQuadBatch &batch) - { - return m_gl.createPlainQuadBatch(batch); - } - NODISCARD RoomTintArray resolve(const RoomTintArray &arr) + NODISCARD RoomTintArray resolve(const RoomTintArray &arr) { RoomTintArray result; for (const RoomTintEnum tint : ALL_ROOM_TINTS) { - const PlainQuadBatch &b = arr[tint]; - result[tint] = m_gl.createPlainQuadBatch(b); + const Fn &fn = arr[tint]; + result[tint] = resolve(fn); } return result; } @@ -967,7 +1004,9 @@ LayerMeshes LayerMeshesIntermediate::getLayerMeshes(OpenGL &gl) const return lm; } -void LayerMeshes::render(const int thisLayer, const int focusedLayer) +void LayerMeshes::render(const int thisLayer, + const int focusedLayer, + const GLuint named_colors_buffer_id) { // Disable texturing for this layer. We want to draw // all of the squares in white (using layer boost quads), @@ -975,9 +1014,15 @@ void LayerMeshes::render(const int thisLayer, const int focusedLayer) const bool disableTextures = (thisLayer > focusedLayer) && !getConfig().canvas.drawUpperLayersTextured; - const GLRenderState less = GLRenderState().withDepthFunction(DepthFunctionEnum::LESS); - const GLRenderState equal = GLRenderState().withDepthFunction(DepthFunctionEnum::EQUAL); - const GLRenderState lequal = GLRenderState().withDepthFunction(DepthFunctionEnum::LEQUAL); + const auto defaultState = std::invoke([named_colors_buffer_id]() -> GLRenderState { + GLRenderState rs; + rs.uniforms.namedColorBufferObject = named_colors_buffer_id; + return rs; + }); + + const GLRenderState less = defaultState.withDepthFunction(DepthFunctionEnum::LESS); + const GLRenderState equal = defaultState.withDepthFunction(DepthFunctionEnum::EQUAL); + const GLRenderState lequal = defaultState.withDepthFunction(DepthFunctionEnum::LEQUAL); const GLRenderState less_blended = less.withBlend(BlendModeEnum::TRANSPARENCY); const GLRenderState lequal_blended = lequal.withBlend(BlendModeEnum::TRANSPARENCY); @@ -1003,20 +1048,8 @@ void LayerMeshes::render(const int thisLayer, const int focusedLayer) // REVISIT: move trails to their own batch also colored by the tint? for (const RoomTintEnum tint : ALL_ROOM_TINTS) { static_assert(NUM_ROOM_TINTS == 2); - const auto namedColor = std::invoke([tint]() -> NamedColorEnum { - switch (tint) { - case RoomTintEnum::DARK: - return LOOKUP_COLOR(ROOM_DARK); - case RoomTintEnum::NO_SUNDEATH: - return LOOKUP_COLOR(ROOM_NO_SUNDEATH); - } - std::abort(); - }); - - if (const auto optColor = getColor(namedColor)) { - tints[tint].render(equal_multiplied.withColor(optColor.value())); - } else { - assert(false); + if (const UniqueMesh &mesh = tints[tint]) { + mesh.render(equal_multiplied); } } diff --git a/src/display/Textures.cpp b/src/display/Textures.cpp index 5b02e844d..ac0ebe44c 100644 --- a/src/display/Textures.cpp +++ b/src/display/Textures.cpp @@ -283,6 +283,12 @@ void MapCanvas::initTextures() textures.stream_out[dir] = loadTexture( getPixmapFilenameRaw(QString::asprintf("stream-out-%s.png", lowercaseDirection(dir)))); } + { + // 1x1 + QImage whitePixel(1, 1, QImage::Format_RGBA8888); + whitePixel.fill(Qt::white); + textures.white_pixel = MMTexture::alloc(std::vector{whitePixel}); + } // char images are 256 textures.char_arrows = loadTexture(getPixmapFilenameRaw("char-arrows.png")); @@ -458,6 +464,13 @@ void MapCanvas::initTextures() textures.terrain_Array = textures.road_Array = pArrayTex; } + { + SharedMMTexture pArrayTex; + std::vector pixels{textures.white_pixel}; + maybeCreateArray2(pixels, pArrayTex); + textures.white_pixel_Array = pArrayTex; + } + { using Four = std::array; Four exits{textures.exit_climb_down, diff --git a/src/display/Textures.h b/src/display/Textures.h index 7d00ccddb..2672a0268 100644 --- a/src/display/Textures.h +++ b/src/display/Textures.h @@ -179,7 +179,8 @@ using TextureArrayNESWUD = EnumIndexedArray; + struct NODISCARD MaybeDataOrMesh final - : public std::variant + : public std::variant { public: - using base = std::variant; + using base = std::variant; using base::base; public: NODISCARD bool empty() const { return std::holds_alternative(*this); } - NODISCARD bool hasData() const - { - return std::holds_alternative(*this); - } + NODISCARD bool hasData() const { return std::holds_alternative(*this); } NODISCARD bool hasMesh() const { return std::holds_alternative(*this); } public: - NODISCARD const ColoredTexVertVector &getData() const + NODISCARD const DiffQuadVector &getData() const { - return std::get(*this); + return std::get(*this); } NODISCARD const UniqueMesh &getMesh() const { return std::get(*this); } - void render(OpenGL &gl, const MMTextureId texId) + void render(MapCanvas &mc, OpenGL &gl, const MMTextureId texId) { if (empty()) { assert(false); @@ -96,7 +95,7 @@ class NODISCARD_QOBJECT MapCanvas final : public QOpenGLWidget, } if (hasData()) { - *this = gl.createColoredTexturedQuadBatch(getData(), texId); + *this = gl.createRoomQuadTexBatch(getData(), texId); assert(hasMesh()); // REVISIT: rendering immediately after uploading the mesh may lag, // so consider delaying until the data is already on the GPU. @@ -107,7 +106,7 @@ class NODISCARD_QOBJECT MapCanvas final : public QOpenGLWidget, return; } auto &mesh = getMesh(); - mesh.render(GLRenderState().withBlend(BlendModeEnum::TRANSPARENCY)); + mesh.render(mc.getDefaultRenderState().withBlend(BlendModeEnum::TRANSPARENCY)); } }; @@ -145,6 +144,8 @@ class NODISCARD_QOBJECT MapCanvas final : public QOpenGLWidget, FrameRateController m_frameRateController; std::unique_ptr m_logger; Signal2Lifetime m_lifetime; + GLuint m_named_colors_buffer_id = 0; + bool m_namedColorsDirty = true; struct AltDragState { @@ -216,6 +217,8 @@ class NODISCARD_QOBJECT MapCanvas final : public QOpenGLWidget, void initTextures(); void updateTextures(); void updateMultisampling(); + NODISCARD GLRenderState getDefaultRenderState(); + void updateNamedColorsUBO(); NODISCARD std::shared_ptr getInfomarkSelection(const MouseSel &sel); NODISCARD static glm::mat4 getViewProj_old(const glm::vec2 &scrollPos, diff --git a/src/display/mapcanvas_gl.cpp b/src/display/mapcanvas_gl.cpp index 460321f03..f0613c2b3 100644 --- a/src/display/mapcanvas_gl.cpp +++ b/src/display/mapcanvas_gl.cpp @@ -248,12 +248,7 @@ void MapCanvas::initializeGL() auto &sharedFuncs = gl.getSharedFunctions(Badge{}); Legacy::Functions &funcs = deref(sharedFuncs); Legacy::ShaderPrograms &programs = funcs.getShaderPrograms(); - std::ignore = programs.getPlainAColorShader(); - std::ignore = programs.getPlainUColorShader(); - std::ignore = programs.getTexturedAColorShader(); - std::ignore = programs.getTexturedUColorShader(); - std::ignore = programs.getFontShader(); - std::ignore = programs.getPointShader(); + programs.early_init(); } setConfig().canvas.showUnsavedChanges.registerChangeCallback(m_lifetime, [this]() { @@ -628,6 +623,8 @@ void MapCanvas::finishPendingMapBatches() .arg(msg); qWarning().noquote() << s; global::sendToUser(s); + + // FIXME: This causes a cycle when the remeshing throws. m_data.restoreSnapshot(); } #undef LOG @@ -637,6 +634,7 @@ void MapCanvas::actuallyPaintGL() { // DECL_TIMER(t, __FUNCTION__); setViewportAndMvp(width(), height()); + updateNamedColorsUBO(); auto &gl = getOpenGL(); @@ -711,45 +709,23 @@ void MapCanvas::Diff::maybeAsyncUpdate(const Map &saved, const Map ¤t) const bool showNeedsServerId = canvas.showMissingMapId.get(); const bool showChanged = canvas.showUnsavedChanges.get(); - // Note: Eventually just want to use NamedColorEnum and then reading from the - // NamedColors Uniform Buffer instead of making copies of the current values. - const struct NODISCARD HighlightColors final - { - Color HIGHLIGHT_TEMPORARY = XNamedColor{NamedColorEnum::HIGHLIGHT_TEMPORARY}.getColor(); - Color HIGHLIGHT_NEEDS_SERVER_ID = XNamedColor{NamedColorEnum::HIGHLIGHT_NEEDS_SERVER_ID} - .getColor(); - Color HIGHLIGHT_UNSAVED = XNamedColor{NamedColorEnum::HIGHLIGHT_UNSAVED}.getColor(); - } colors; - diff.futureHighlight = std::async( std::launch::async, - [saved, current, showNeedsServerId, showChanged, colors]() -> Diff::HighlightDiff { + [saved, current, showNeedsServerId, showChanged]() -> Diff::HighlightDiff { DECL_TIMER(t2, "[async] actuallyPaintGL: highlight changes, temporary, and needs update"); - // 3-2 - // |/| - // 0-1 - static constexpr std::array corners{ - glm::vec3{0, 0, 0}, - glm::vec3{1, 0, 0}, - glm::vec3{1, 1, 0}, - glm::vec3{0, 1, 0}, - }; - - auto getHighlights = [&saved, ¤t, showChanged, showNeedsServerId, &colors]() - -> Diff::MaybeDataOrMesh { + auto getHighlights = + [&saved, ¤t, showChanged, showNeedsServerId]() -> Diff::MaybeDataOrMesh { if (!showChanged && !showNeedsServerId) { return Diff::MaybeDataOrMesh{}; } DECL_TIMER(t3, "[async] actuallyPaintGL: compute highlights"); - ColoredTexVertVector highlights; - auto drawQuad = [&highlights](const RawRoom &room, const Color color) { + DiffQuadVector highlights; + auto drawQuad = [&highlights](const RawRoom &room, const NamedColorEnum color) { const auto &pos = room.getPosition().to_vec3(); - for (auto &corner : corners) { - highlights.emplace_back(color, corner, pos + corner); - } + highlights.emplace_back(pos, 0, color); }; // Handle rooms needing a server ID or that are temporary @@ -757,9 +733,9 @@ void MapCanvas::Diff::maybeAsyncUpdate(const Map &saved, const Map ¤t) current.getRooms().for_each([&](auto id) { if (auto h = current.getRoomHandle(id)) { if (h.isTemporary()) { - drawQuad(h.getRaw(), colors.HIGHLIGHT_TEMPORARY); + drawQuad(h.getRaw(), NamedColorEnum::HIGHLIGHT_TEMPORARY); } else if (h.getServerId() == INVALID_SERVER_ROOMID) { - drawQuad(h.getRaw(), colors.HIGHLIGHT_NEEDS_SERVER_ID); + drawQuad(h.getRaw(), NamedColorEnum::HIGHLIGHT_NEEDS_SERVER_ID); } } }); @@ -769,7 +745,7 @@ void MapCanvas::Diff::maybeAsyncUpdate(const Map &saved, const Map ¤t) if (showChanged) { ProgressCounter dummyPc; Map::foreachChangedRoom(dummyPc, saved, current, [&](const RawRoom &room) { - drawQuad(room, colors.HIGHLIGHT_UNSAVED); + drawQuad(room, NamedColorEnum::HIGHLIGHT_UNSAVED); }); } @@ -798,7 +774,7 @@ void MapCanvas::paintDifferences() auto &gl = getOpenGL(); if (auto &highlights = highlight.highlights; !highlights.empty()) { - highlights.render(gl, m_textures.room_highlight->getArrayPosition().array); + highlights.render(*this, gl, m_textures.room_highlight->getArrayPosition().array); } } @@ -1034,6 +1010,40 @@ void MapCanvas::updateMultisampling() getOpenGL().configureFbo(wantMultisampling); } +void MapCanvas::updateNamedColorsUBO() +{ + auto &gl = getOpenGL(); + static std::weak_ptr g_weak_vbo; + std::shared_ptr shared_vbo = g_weak_vbo.lock(); + if (shared_vbo == nullptr) { + auto &fns = gl.getSharedFunctions(Badge{}); + + g_weak_vbo = shared_vbo = fns->getStaticVbos().alloc(); + Legacy::VBO &vbo = deref(shared_vbo); + vbo.emplace(fns); + m_namedColorsDirty = true; + } + + if (!m_namedColorsDirty) { + return; + } + + m_named_colors_buffer_id = std::invoke([&gl, &shared_vbo]() { + auto &fns = gl.getSharedFunctions(Badge{}); + Legacy::VBO &vbo = deref(shared_vbo); + + // the shader is declared vec4, so the data has to be 4 floats per entry. + const auto &vec4_colors = XNamedColor::getAllColorsAsVec4(); + + MAYBE_UNUSED const auto result = fns->setUbo(vbo.get(), + vec4_colors, + BufferUsageEnum::DYNAMIC_DRAW); + assert(result == static_cast(vec4_colors.size())); + return vbo.get(); + }); + m_namedColorsDirty = false; +} + void MapCanvas::renderMapBatches() { std::optional &mapBatches = m_batches.mapBatches; @@ -1051,25 +1061,24 @@ void MapCanvas::renderMapBatches() && (totalScaleFactor >= settings.doorNameScaleCutoff); auto &gl = getOpenGL(); + updateNamedColorsUBO(); BatchedMeshes &batchedMeshes = batches.batchedMeshes; const auto drawLayer = - [&batches, &batchedMeshes, wantExtraDetail, wantDoorNames](const int thisLayer, - const int currentLayer) { + [this, &batches, &batchedMeshes, wantExtraDetail, wantDoorNames](const int thisLayer, + const int currentLayer) { const auto it_mesh = batchedMeshes.find(thisLayer); if (it_mesh != batchedMeshes.end()) { LayerMeshes &meshes = it_mesh->second; - meshes.render(thisLayer, currentLayer); + meshes.render(thisLayer, currentLayer, m_named_colors_buffer_id); } if (wantExtraDetail) { - { - BatchedConnectionMeshes &connectionMeshes = batches.connectionMeshes; - const auto it_conn = connectionMeshes.find(thisLayer); - if (it_conn != connectionMeshes.end()) { - ConnectionMeshes &meshes = it_conn->second; - meshes.render(thisLayer, currentLayer); - } + BatchedConnectionMeshes &connectionMeshes = batches.connectionMeshes; + const auto it_conn = connectionMeshes.find(thisLayer); + if (it_conn != connectionMeshes.end()) { + ConnectionMeshes &meshes = it_conn->second; + meshes.render(thisLayer, currentLayer); } // NOTE: This can display room names in lower layers, but the text @@ -1104,3 +1113,10 @@ void MapCanvas::renderMapBatches() drawLayer(thisLayer, m_currentLayer); } } + +GLRenderState MapCanvas::getDefaultRenderState() +{ + GLRenderState defaultState; + defaultState.uniforms.namedColorBufferObject = m_named_colors_buffer_id; + return defaultState; +} diff --git a/src/global/AsyncTasks.cpp b/src/global/AsyncTasks.cpp index 8b8b7764d..f62d86568 100644 --- a/src/global/AsyncTasks.cpp +++ b/src/global/AsyncTasks.cpp @@ -3,8 +3,8 @@ #include "AsyncTasks.h" +#include "../global/PrintUtils.h" #include "ConfigConsts.h" -#include "global/PrintUtils.h" #include "logging.h" #include "thread_utils.h" diff --git a/src/global/NamedColors.cpp b/src/global/NamedColors.cpp index c5238e543..8cd093e0f 100644 --- a/src/global/NamedColors.cpp +++ b/src/global/NamedColors.cpp @@ -17,11 +17,13 @@ struct NODISCARD GlobalData final using InitArray = EnumIndexedArray; using Map = std::map; using NamesVector = std::vector; + using Vec4Vector = std::vector; private: Map m_map; NamesVector m_names; ColorVector m_colors; + Vec4Vector m_vec4s; InitArray m_initialized; private: @@ -31,6 +33,7 @@ struct NODISCARD GlobalData final GlobalData() { m_colors.resize(NUM_NAMED_COLORS); + m_vec4s.resize(MAX_NAMED_COLORS); m_names.resize(NUM_NAMED_COLORS); static const auto white = Colors::white; @@ -43,6 +46,7 @@ struct NODISCARD GlobalData final const auto color = (id == NamedColorEnum::TRANSPARENT) ? transparent_black : white; m_colors[idx] = color; + m_vec4s[idx] = color.getVec4(); m_names[idx] = name; m_map.emplace(name, id); }; @@ -69,6 +73,7 @@ struct NODISCARD GlobalData final const auto idx = getIndex(id); m_colors.at(idx) = c; + m_vec4s.at(idx) = c.getVec4(); m_initialized.at(id) = true; return true; } @@ -81,6 +86,7 @@ struct NODISCARD GlobalData final NODISCARD const std::vector &getAllNames() const { return m_names; } NODISCARD const std::vector &getAllColors() const { return m_colors; } + NODISCARD const std::vector &getAllVec4s() const { return m_vec4s; } public: NODISCARD std::optional lookup(std::string_view name) const @@ -139,3 +145,8 @@ const std::vector &XNamedColor::getAllColors() { return getGlobalData().getAllColors(); } + +const std::vector &XNamedColor::getAllColorsAsVec4() +{ + return getGlobalData().getAllVec4s(); +} diff --git a/src/global/NamedColors.h b/src/global/NamedColors.h index 8f366b480..7a7c5733e 100644 --- a/src/global/NamedColors.h +++ b/src/global/NamedColors.h @@ -48,6 +48,9 @@ enum class NODISCARD NamedColorEnum : uint8_t { DEFAULT = 0, XFOREACH_NAMED_COLO static inline constexpr size_t NUM_NAMED_COLORS = XFOREACH_NAMED_COLOR_OPTIONS(X_COUNT) + 1; #undef X_COUNT +static inline constexpr size_t MAX_NAMED_COLORS = 32; +static_assert(MAX_NAMED_COLORS >= NUM_NAMED_COLORS); + // TODO: rename this, but to what? NamedColorHandle? class NODISCARD XNamedColor final { @@ -98,5 +101,6 @@ class NODISCARD XNamedColor final public: NODISCARD static const std::vector &getAllColors(); + NODISCARD static const std::vector &getAllColorsAsVec4(); NODISCARD static const std::vector &getAllNames(); }; diff --git a/src/map/ExitDirection.cpp b/src/map/ExitDirection.cpp index 57bbc2338..b7acf7c5e 100644 --- a/src/map/ExitDirection.cpp +++ b/src/map/ExitDirection.cpp @@ -5,11 +5,11 @@ #include "ExitDirection.h" #include "../global/Array.h" +#include "../global/Charset.h" #include "../global/Consts.h" #include "../global/EnumIndexedArray.h" #include "../global/enums.h" #include "coordinate.h" -#include "global/Charset.h" namespace enums { const MMapper::Array &getAllExitsNESW() diff --git a/src/opengl/OpenGL.cpp b/src/opengl/OpenGL.cpp index 6677633b3..0c38d589f 100644 --- a/src/opengl/OpenGL.cpp +++ b/src/opengl/OpenGL.cpp @@ -133,6 +133,12 @@ UniqueMesh OpenGL::createColoredTexturedQuadBatch(const std::vector &batch, + const MMTextureId texture) +{ + return getFunctions().createRoomQuadTexBatch(batch, texture); +} + UniqueMesh OpenGL::createFontMesh(const SharedMMTexture &texture, const DrawModeEnum mode, const std::vector &batch) diff --git a/src/opengl/OpenGL.h b/src/opengl/OpenGL.h index 1583cefb3..6c15e4cfd 100644 --- a/src/opengl/OpenGL.h +++ b/src/opengl/OpenGL.h @@ -89,6 +89,11 @@ class NODISCARD OpenGL final NODISCARD UniqueMesh createColoredTexturedQuadBatch(const std::vector &verts, MMTextureId texture); +public: + NODISCARD UniqueMesh createRoomQuadTexBatch(const std::vector &verts, + MMTextureId texture); + +public: NODISCARD UniqueMesh createFontMesh(const SharedMMTexture &texture, DrawModeEnum mode, const std::vector &batch); diff --git a/src/opengl/OpenGLTypes.h b/src/opengl/OpenGLTypes.h index cc9941f07..b768290aa 100644 --- a/src/opengl/OpenGLTypes.h +++ b/src/opengl/OpenGLTypes.h @@ -4,13 +4,13 @@ #include "../global/Array.h" #include "../global/Color.h" +#include "../global/ConfigConsts.h" #include "../global/IndexedVector.h" #include "../global/NamedColors.h" #include "../global/TaggedInt.h" #include "../global/hash.h" #include "../global/utils.h" #include "FontFormatFlags.h" -#include "global/ConfigConsts.h" #include #include @@ -51,6 +51,29 @@ struct NODISCARD ColoredTexVert final {} }; +struct NODISCARD RoomQuadTexVert final +{ + // xyz = room coord, w = (colorId << 8 | tex_z) + // - colorId: packed into bits 8-15 (must be < MAX_NAMED_COLORS) + // - tex_z: packed into bits 0-7 (must be < 256) + glm::ivec4 vertTexCol{}; + + explicit RoomQuadTexVert(const glm::ivec3 &vert, const int tex_z) + : RoomQuadTexVert{vert, tex_z, NamedColorEnum::DEFAULT} + {} + + explicit RoomQuadTexVert(const glm::ivec3 &vert, const int tex_z, const NamedColorEnum color) + : vertTexCol{vert, (static_cast(color) << 8) | tex_z} + { + if (IS_DEBUG_BUILD) { + constexpr auto max_texture_layers = 256; + const auto c = static_cast(color); + assert(c == std::clamp(c, 0, NUM_NAMED_COLORS - 1)); + assert(tex_z == std::clamp(tex_z, 0, max_texture_layers - 1)); + } + } +}; + using ColoredTexVertVector = std::vector; struct NODISCARD ColorVert final @@ -88,7 +111,14 @@ struct NODISCARD FontVert3d final {} }; -enum class NODISCARD DrawModeEnum { INVALID = 0, POINTS = 1, LINES = 2, TRIANGLES = 3, QUADS = 4 }; +enum class NODISCARD DrawModeEnum { + INVALID = 0, + POINTS = 1, + LINES = 2, + TRIANGLES = 3, + QUADS = 4, + INSTANCED_QUADS = 5 +}; struct NODISCARD LineParams final { @@ -192,6 +222,7 @@ struct NODISCARD GLRenderState final Color color; // glEnable(TEXTURE_2D), or glEnable(TEXTURE_3D) Textures textures; + GLuint namedColorBufferObject = 0u; std::optional pointSize; }; @@ -344,6 +375,7 @@ class NODISCARD UniqueMesh final DEFAULT_MOVES_DELETE_COPIES(UniqueMesh); void render(const GLRenderState &rs) const { deref(m_mesh).render(rs); } + NODISCARD explicit operator bool() const { return m_mesh != nullptr; } }; struct NODISCARD UniqueMeshVector final diff --git a/src/opengl/legacy/AbstractShaderProgram.cpp b/src/opengl/legacy/AbstractShaderProgram.cpp index 15c21ee3b..e263e0a6f 100644 --- a/src/opengl/legacy/AbstractShaderProgram.cpp +++ b/src/opengl/legacy/AbstractShaderProgram.cpp @@ -3,6 +3,8 @@ #include "AbstractShaderProgram.h" +#include "../../global/ConfigConsts.h" + namespace Legacy { AbstractShaderProgram::AbstractShaderProgram(std::string dirName, @@ -160,6 +162,16 @@ void AbstractShaderProgram::setTexture(const char *const name, const int texture setUniform1iv(uFontTextureLoc, 1, &textureUnit); } +void AbstractShaderProgram::setUBO(const char *const block_name, const GLuint uboId) +{ + assert(uboId != 0); + auto functions = m_functions.lock(); + const GLuint program = m_program.get(); + auto block_index = functions->glGetUniformBlockIndex(program, block_name); + functions->glUniformBlockBinding(program, block_index, 0); + deref(functions).glBindBufferBase(GL_UNIFORM_BUFFER, 0, uboId); +} + void AbstractShaderProgram::setViewport(const char *const name, const Viewport &input_viewport) { const glm::ivec4 viewport{input_viewport.offset, input_viewport.size}; diff --git a/src/opengl/legacy/AbstractShaderProgram.h b/src/opengl/legacy/AbstractShaderProgram.h index c5c8b880e..5ca874037 100644 --- a/src/opengl/legacy/AbstractShaderProgram.h +++ b/src/opengl/legacy/AbstractShaderProgram.h @@ -88,6 +88,7 @@ struct NODISCARD AbstractShaderProgram void setColor(const char *name, Color color); void setMatrix(const char *name, const glm::mat4 &m); void setTexture(const char *name, int textureUnit); + void setUBO(const char *block_name, GLuint uboId); // make this type stronger? void setViewport(const char *name, const Viewport &input_viewport); }; diff --git a/src/opengl/legacy/FunctionsES30.cpp b/src/opengl/legacy/FunctionsES30.cpp index dd5379c12..1bdcc32a3 100644 --- a/src/opengl/legacy/FunctionsES30.cpp +++ b/src/opengl/legacy/FunctionsES30.cpp @@ -25,6 +25,7 @@ std::optional FunctionsES30::virt_toGLenum(const DrawModeEnum mode) case DrawModeEnum::INVALID: case DrawModeEnum::QUADS: + case DrawModeEnum::INSTANCED_QUADS: break; } diff --git a/src/opengl/legacy/FunctionsGL33.cpp b/src/opengl/legacy/FunctionsGL33.cpp index 78f331fbc..bbe1d5ba0 100644 --- a/src/opengl/legacy/FunctionsGL33.cpp +++ b/src/opengl/legacy/FunctionsGL33.cpp @@ -1,5 +1,6 @@ #include "FunctionsGL33.h" +#include "../../global/ConfigConsts.h" #include "../OpenGLConfig.h" #include @@ -27,7 +28,10 @@ std::optional FunctionsGL33::virt_toGLenum(const DrawModeEnum mode) case DrawModeEnum::QUADS: #ifndef MMAPPER_NO_OPENGL return canRenderQuads() ? std::make_optional(GL_QUADS) : std::nullopt; +#else + FALLTHROUGH; #endif + case DrawModeEnum::INSTANCED_QUADS: case DrawModeEnum::INVALID: break; } diff --git a/src/opengl/legacy/Legacy.cpp b/src/opengl/legacy/Legacy.cpp index 94ee7fc85..40a9b924a 100644 --- a/src/opengl/legacy/Legacy.cpp +++ b/src/opengl/legacy/Legacy.cpp @@ -107,6 +107,14 @@ UniqueMesh Functions::createColoredTexturedBatch(const DrawModeEnum mode, return createTexturedMesh(shared_from_this(), mode, batch, prog, texture); } +UniqueMesh Functions::createRoomQuadTexBatch(const std::vector &batch, + const MMTextureId texture) +{ + const auto mode = DrawModeEnum::INSTANCED_QUADS; + const auto &prog = getShaderPrograms().getRoomQuadTexShader(); + return createTexturedMesh(shared_from_this(), mode, batch, prog, texture); +} + template typename Mesh_, typename ShaderType_> static void renderImmediate(const SharedFunctions &sharedFunctions, const DrawModeEnum mode, diff --git a/src/opengl/legacy/Legacy.h b/src/opengl/legacy/Legacy.h index f3b80ebdc..9e3ef14c3 100644 --- a/src/opengl/legacy/Legacy.h +++ b/src/opengl/legacy/Legacy.h @@ -140,6 +140,7 @@ class NODISCARD Functions : protected QOpenGLExtraFunctions, using Base::glActiveTexture; using Base::glAttachShader; using Base::glBindBuffer; + using Base::glBindBufferBase; using Base::glBindTexture; using Base::glBindVertexArray; using Base::glBlendFunc; @@ -160,6 +161,7 @@ class NODISCARD Functions : protected QOpenGLExtraFunctions, using Base::glDisable; using Base::glDisableVertexAttribArray; using Base::glDrawArrays; + using Base::glDrawElementsInstanced; using Base::glEnable; using Base::glEnableVertexAttribArray; using Base::glGenBuffers; @@ -174,6 +176,7 @@ class NODISCARD Functions : protected QOpenGLExtraFunctions, using Base::glGetString; using Base::glGetTexLevelParameteriv; using Base::glGetTexParameteriv; + using Base::glGetUniformBlockIndex; using Base::glGetUniformLocation; using Base::glHint; using Base::glIsBuffer; @@ -188,8 +191,10 @@ class NODISCARD Functions : protected QOpenGLExtraFunctions, using Base::glUniform1iv; using Base::glUniform4fv; using Base::glUniform4iv; + using Base::glUniformBlockBinding; using Base::glUniformMatrix4fv; using Base::glUseProgram; + using Base::glVertexAttribDivisor; using Base::glVertexAttribPointer; public: @@ -264,18 +269,19 @@ class NODISCARD Functions : protected QOpenGLExtraFunctions, NODISCARD virtual const char *virt_getShaderVersion() const = 0; private: - template - NODISCARD GLsizei setVbo_internal(const GLuint vbo, - const std::vector &batch, - const BufferUsageEnum usage) + template + NODISCARD GLsizei setBufferData_internal(const GLenum target, + const GLuint buffer, + const std::vector &batch, + const BufferUsageEnum usage) { - const auto numVerts = static_cast(batch.size()); - const auto vertSize = static_cast(sizeof(VertexType_)); - const auto numBytes = numVerts * vertSize; - Base::glBindBuffer(GL_ARRAY_BUFFER, vbo); - Base::glBufferData(GL_ARRAY_BUFFER, numBytes, batch.data(), Legacy::toGLenum(usage)); - Base::glBindBuffer(GL_ARRAY_BUFFER, 0); - return numVerts; + const auto numElements = static_cast(batch.size()); + const auto elementSize = static_cast(sizeof(T)); + const auto numBytes = numElements * elementSize; + Base::glBindBuffer(target, buffer); + Base::glBufferData(target, numBytes, batch.data(), Legacy::toGLenum(usage)); + Base::glBindBuffer(target, 0); + return numElements; } public: @@ -297,6 +303,16 @@ class NODISCARD Functions : protected QOpenGLExtraFunctions, Base::glVertexAttribPointer(index, size, type, normalized, stride, pointer); } + void enableAttribI(const GLuint index, + const GLint size, + const GLenum type, + const GLsizei stride, + const GLvoid *const pointer) + { + Base::glEnableVertexAttribArray(index); + Base::glVertexAttribIPointer(index, size, type, stride, pointer); + } + template NODISCARD std::pair setVbo( const DrawModeEnum mode, @@ -306,9 +322,28 @@ class NODISCARD Functions : protected QOpenGLExtraFunctions, { if (mode == DrawModeEnum::QUADS && !canRenderQuads()) { return std::pair(DrawModeEnum::TRIANGLES, - setVbo_internal(vbo, convertQuadsToTris(batch), usage)); + setBufferData_internal(GL_ARRAY_BUFFER, + vbo, + convertQuadsToTris(batch), + usage)); } - return std::pair(mode, setVbo_internal(vbo, batch, usage)); + return std::pair(mode, setBufferData_internal(GL_ARRAY_BUFFER, vbo, batch, usage)); + } + + template + NODISCARD GLsizei setIbo(const GLuint ibo, + const std::vector &batch, + const BufferUsageEnum usage = BufferUsageEnum::STATIC_DRAW) + { + return setBufferData_internal(GL_ELEMENT_ARRAY_BUFFER, ibo, batch, usage); + } + + template + NODISCARD GLsizei setUbo(const GLuint ubo, + const std::vector &batch, + const BufferUsageEnum usage = BufferUsageEnum::DYNAMIC_DRAW) + { + return setBufferData_internal(GL_UNIFORM_BUFFER, ubo, batch, usage); } void clearVbo(const GLuint vbo, const BufferUsageEnum usage = BufferUsageEnum::DYNAMIC_DRAW) @@ -331,6 +366,10 @@ class NODISCARD Functions : protected QOpenGLExtraFunctions, const std::vector &batch, MMTextureId texture); +public: + NODISCARD UniqueMesh createRoomQuadTexBatch(const std::vector &batch, + MMTextureId texture); + public: NODISCARD UniqueMesh createFontMesh(const SharedMMTexture &texture, DrawModeEnum mode, diff --git a/src/opengl/legacy/Meshes.h b/src/opengl/legacy/Meshes.h index 418fcef5e..8d0b8a107 100644 --- a/src/opengl/legacy/Meshes.h +++ b/src/opengl/legacy/Meshes.h @@ -153,7 +153,6 @@ class NODISCARD TexturedMesh final : public SimpleMesh +class NODISCARD RoomQuadTexMesh final : public SimpleMesh +{ +public: + using Base = SimpleMesh; + using Base::Base; + +private: + struct NODISCARD Attribs final + { + GLuint vertTexColPos = INVALID_ATTRIB_LOCATION; + + NODISCARD static Attribs getLocations(RoomQuadTexShader &fontShader) + { + Attribs result; + result.vertTexColPos = fontShader.getAttribLocation("aVertTexCol"); + return result; + } + }; + + std::optional m_boundAttribs; + + void virt_bind() override + { + const auto vertSize = static_cast(sizeof(VertexType_)); + static_assert(sizeof(std::declval().vertTexCol) == 4 * sizeof(int32_t)); + + Functions &gl = Base::m_functions; + const auto attribs = Attribs::getLocations(Base::m_program); + gl.glBindBuffer(GL_ARRAY_BUFFER, Base::m_vbo.get()); + // ivec4 + gl.enableAttribI(attribs.vertTexColPos, 4, GL_INT, vertSize, VPO(vertTexCol)); + + // instancing + gl.glVertexAttribDivisor(attribs.vertTexColPos, 1); + + m_boundAttribs = attribs; + } + + void virt_unbind() override + { + if (!m_boundAttribs) { + assert(false); + return; + } + + auto &attribs = m_boundAttribs.value(); + Functions &gl = Base::m_functions; + gl.glDisableVertexAttribArray(attribs.vertTexColPos); + gl.glBindBuffer(GL_ARRAY_BUFFER, 0); + m_boundAttribs.reset(); + } +}; + // Per-vertex color // flat-shaded in MMapper, due to glShadeModel(GL_FLAT) template diff --git a/src/opengl/legacy/ShaderUtils.cpp b/src/opengl/legacy/ShaderUtils.cpp index b1f99705d..ba302c20b 100644 --- a/src/opengl/legacy/ShaderUtils.cpp +++ b/src/opengl/legacy/ShaderUtils.cpp @@ -5,6 +5,7 @@ #include "../../global/ConfigConsts.h" #include "../../global/Consts.h" +#include "../../global/NamedColors.h" #include "../../global/PrintUtils.h" #include "../../global/TextUtils.h" @@ -211,7 +212,12 @@ NODISCARD static GLuint compileShader(Functions &gl, const GLenum type, const So // NOTE: GLES 2.0 required `const char**` instead of `const char*const*`, // so Qt uses the least common denominator without the middle const; // that's the reason the `ptrs` array below is not `const`. - std::array ptrs = {gl.getShaderVersion(), "#line 1\n", source.source.c_str()}; + std::string defineNamedColors = "#define MAX_NAMED_COLORS " + std::to_string(MAX_NAMED_COLORS) + + "\n"; + std::array ptrs = {gl.getShaderVersion(), + defineNamedColors.c_str(), + "#line 1\n", + source.source.c_str()}; gl.glShaderSource(shaderId, static_cast(ptrs.size()), ptrs.data(), nullptr); gl.glCompileShader(shaderId); checkShaderInfo(gl, shaderId); diff --git a/src/opengl/legacy/Shaders.cpp b/src/opengl/legacy/Shaders.cpp index ba4b0ad7c..10513ff58 100644 --- a/src/opengl/legacy/Shaders.cpp +++ b/src/opengl/legacy/Shaders.cpp @@ -31,9 +31,38 @@ AColorPlainShader::~AColorPlainShader() = default; UColorPlainShader::~UColorPlainShader() = default; AColorTexturedShader::~AColorTexturedShader() = default; UColorTexturedShader::~UColorTexturedShader() = default; + +RoomQuadTexShader::~RoomQuadTexShader() = default; + FontShader::~FontShader() = default; PointShader::~PointShader() = default; +void ShaderPrograms::early_init() +{ + std::ignore = getPlainAColorShader(); + std::ignore = getPlainUColorShader(); + std::ignore = getTexturedAColorShader(); + std::ignore = getTexturedUColorShader(); + + std::ignore = getRoomQuadTexShader(); + + std::ignore = getFontShader(); + std::ignore = getPointShader(); +} + +void ShaderPrograms::resetAll() +{ + m_aColorShader.reset(); + m_uColorShader.reset(); + m_aTexturedShader.reset(); + m_uTexturedShader.reset(); + + m_roomQuadTexShader.reset(); + + m_font.reset(); + m_point.reset(); +} + // essentially a private member of ShaderPrograms template NODISCARD static std::shared_ptr loadSimpleShaderProgram(Functions &functions, @@ -78,6 +107,11 @@ const std::shared_ptr &ShaderPrograms::getTexturedAColorSh return getInitialized(m_aTexturedShader, getFunctions(), "tex/acolor"); } +const std::shared_ptr &ShaderPrograms::getRoomQuadTexShader() +{ + return getInitialized(m_roomQuadTexShader, getFunctions(), "room/tex/acolor"); +} + const std::shared_ptr &ShaderPrograms::getTexturedUColorShader() { return getInitialized(m_uTexturedShader, getFunctions(), "tex/ucolor"); diff --git a/src/opengl/legacy/Shaders.h b/src/opengl/legacy/Shaders.h index b7a483789..26fdfdb86 100644 --- a/src/opengl/legacy/Shaders.h +++ b/src/opengl/legacy/Shaders.h @@ -56,6 +56,25 @@ struct NODISCARD AColorTexturedShader final : public AbstractShaderProgram } }; +struct NODISCARD RoomQuadTexShader final : public AbstractShaderProgram +{ +public: + using AbstractShaderProgram::AbstractShaderProgram; + + ~RoomQuadTexShader() final; + +private: + void virt_setUniforms(const glm::mat4 &mvp, const GLRenderState::Uniforms &uniforms) final + { + assert(uniforms.textures[0] != INVALID_MM_TEXTURE_ID); + + setColor("uColor", uniforms.color); + setMatrix("uMVP", mvp); + setTexture("uTexture", 0); + setUBO("NamedColorsBlock", uniforms.namedColorBufferObject); + } +}; + struct NODISCARD UColorTexturedShader final : public AbstractShaderProgram { public: @@ -116,10 +135,17 @@ struct NODISCARD ShaderPrograms final { private: Functions &m_functions; + +private: std::shared_ptr m_aColorShader; std::shared_ptr m_uColorShader; std::shared_ptr m_aTexturedShader; std::shared_ptr m_uTexturedShader; + +private: + std::shared_ptr m_roomQuadTexShader; + +private: std::shared_ptr m_font; std::shared_ptr m_point; @@ -134,15 +160,7 @@ struct NODISCARD ShaderPrograms final NODISCARD Functions &getFunctions() { return m_functions; } public: - void resetAll() - { - m_aColorShader.reset(); - m_uColorShader.reset(); - m_aTexturedShader.reset(); - m_uTexturedShader.reset(); - m_font.reset(); - m_point.reset(); - } + void resetAll(); public: // attribute color (aka "Colored") @@ -153,8 +171,16 @@ struct NODISCARD ShaderPrograms final NODISCARD const std::shared_ptr &getTexturedAColorShader(); // uniform color + textured (aka "Textured") NODISCARD const std::shared_ptr &getTexturedUColorShader(); + +public: + NODISCARD const std::shared_ptr &getRoomQuadTexShader(); + +public: NODISCARD const std::shared_ptr &getFontShader(); NODISCARD const std::shared_ptr &getPointShader(); + +public: + void early_init(); }; } // namespace Legacy diff --git a/src/opengl/legacy/SimpleMesh.cpp b/src/opengl/legacy/SimpleMesh.cpp index 2fad5bba3..3aec3119e 100644 --- a/src/opengl/legacy/SimpleMesh.cpp +++ b/src/opengl/legacy/SimpleMesh.cpp @@ -2,3 +2,61 @@ // Copyright (C) 2019 The MMapper Authors #include "SimpleMesh.h" + +#include "../../global/ConfigConsts.h" + +void Legacy::drawRoomQuad(Functions &gl, const GLsizei numVerts) +{ + static constexpr size_t NUM_ELEMENTS = 4; + static std::weak_ptr g_weak_vbo; + + auto shared = g_weak_vbo.lock(); + if (shared == nullptr) { + if (IS_DEBUG_BUILD) { + qDebug() << "allocating shared VBO for drawRoomQuad"; + } + + g_weak_vbo = shared = gl.getStaticVbos().alloc(); + VBO &vbo = deref(shared); + + vbo.emplace(gl.shared_from_this()); + + // CCW order + const std::vector indices{0, 1, 2, 3}; + assert(indices.size() == NUM_ELEMENTS); + + MAYBE_UNUSED const auto numIndices = gl.setIbo(vbo.get(), + indices, + BufferUsageEnum::STATIC_DRAW); + assert(numIndices == NUM_ELEMENTS); + } + + struct NODISCARD IBOBinder final + { + private: + Functions &m_gl; + + public: + IBOBinder(Functions &f, const VBO &vbo) + : m_gl(f) + { + if (IS_DEBUG_BUILD) { + GLint binding = -1; + m_gl.glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &binding); + assert(binding == 0); + } + m_gl.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo.get()); + } + ~IBOBinder() { m_gl.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); } + DELETE_CTORS_AND_ASSIGN_OPS(IBOBinder); + }; + + { + IBOBinder ibo_binder{gl, *shared}; + gl.glDrawElementsInstanced(GL_TRIANGLE_FAN, + NUM_ELEMENTS, + GL_UNSIGNED_BYTE, + nullptr, + numVerts); + } +} diff --git a/src/opengl/legacy/SimpleMesh.h b/src/opengl/legacy/SimpleMesh.h index c59b8f520..850878dfa 100644 --- a/src/opengl/legacy/SimpleMesh.h +++ b/src/opengl/legacy/SimpleMesh.h @@ -17,6 +17,8 @@ namespace Legacy { +void drawRoomQuad(Functions &gl, GLsizei numVerts); + template class NODISCARD SimpleMesh : public IRenderable { @@ -108,7 +110,13 @@ class NODISCARD SimpleMesh : public IRenderable const BufferUsageEnum usage) { const auto numVerts = verts.size(); - assert(mode == DrawModeEnum::INVALID || numVerts % static_cast(mode) == 0); + + static_assert(static_cast(DrawModeEnum::POINTS) == 1); + static_assert(static_cast(DrawModeEnum::LINES) == 2); + static_assert(static_cast(DrawModeEnum::TRIANGLES) == 3); + static_assert(static_cast(DrawModeEnum::QUADS) == 4); + assert(mode == DrawModeEnum::INVALID || mode == DrawModeEnum::INSTANCED_QUADS + || numVerts % static_cast(mode) == 0); if (!m_vbo && numVerts != 0) { m_vbo.emplace(m_shared_functions); @@ -179,11 +187,14 @@ class NODISCARD SimpleMesh : public IRenderable auto attribUnbinder = bindAttribs(); - if (const std::optional &optMode = m_functions.toGLenum(m_drawMode)) { + if (m_drawMode == DrawModeEnum::INSTANCED_QUADS) { + drawRoomQuad(m_functions, m_numVerts); + } else if (const std::optional &optMode = m_functions.toGLenum(m_drawMode)) { m_functions.glDrawArrays(optMode.value(), 0, m_numVerts); } else { assert(false); } } }; + } // namespace Legacy diff --git a/src/parser/SendToUserSourceEnum.h b/src/parser/SendToUserSourceEnum.h index 7b1d58add..fb8859918 100644 --- a/src/parser/SendToUserSourceEnum.h +++ b/src/parser/SendToUserSourceEnum.h @@ -2,7 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later // Copyright (C) 2024 The MMapper Authors -#include "global/macros.h" +#include "../global/macros.h" #include diff --git a/src/resources/mmapper2.qrc b/src/resources/mmapper2.qrc index 843a3408b..33dd3fd75 100644 --- a/src/resources/mmapper2.qrc +++ b/src/resources/mmapper2.qrc @@ -212,6 +212,8 @@ pixmaps/wall-west.png shaders/legacy/font/frag.glsl shaders/legacy/font/vert.glsl + shaders/legacy/room/tex/acolor/frag.glsl + shaders/legacy/room/tex/acolor/vert.glsl shaders/legacy/plain/acolor/frag.glsl shaders/legacy/plain/acolor/vert.glsl shaders/legacy/plain/ucolor/frag.glsl diff --git a/src/resources/shaders/legacy/room/tex/acolor/frag.glsl b/src/resources/shaders/legacy/room/tex/acolor/frag.glsl new file mode 100644 index 000000000..2c6e43927 --- /dev/null +++ b/src/resources/shaders/legacy/room/tex/acolor/frag.glsl @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright (C) 2019 The MMapper Authors + +uniform sampler2DArray uTexture; +uniform vec4 uColor; + +in vec4 vColor; +in vec3 vTexCoord; + +out vec4 vFragmentColor; + +void main() +{ + vFragmentColor = vColor * uColor * texture(uTexture, vTexCoord); +} diff --git a/src/resources/shaders/legacy/room/tex/acolor/vert.glsl b/src/resources/shaders/legacy/room/tex/acolor/vert.glsl new file mode 100644 index 000000000..8121f4e29 --- /dev/null +++ b/src/resources/shaders/legacy/room/tex/acolor/vert.glsl @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright (C) 2019-2025 The MMapper Authors + +uniform mat4 uMVP; +uniform NamedColorsBlock { + vec4 uNamedColors[MAX_NAMED_COLORS]; +}; + +layout(location = 0) in ivec4 aVertTexCol; + +out vec4 vColor; +out vec3 vTexCoord; + +void main() +{ + // ccw-order assumes it's a triangle fan (as opposed to a triangle strip) + const ivec3[4] ioffsets_ccw = ivec3[4](ivec3(0, 0, 0), ivec3(1, 0, 0), ivec3(1, 1, 0), ivec3(0, 1, 0)); + ivec3 ioffset = ioffsets_ccw[gl_VertexID]; + + int texZ = aVertTexCol.w & 0xFF; + int colorId = (aVertTexCol.w >> 8) % MAX_NAMED_COLORS; + + vColor = uNamedColors[colorId]; + vTexCoord = vec3(ioffset.xy, float(texZ)); + gl_Position = uMVP * vec4(aVertTexCol.xyz + ioffset, 1.0); +} diff --git a/src/viewers/AnsiViewWindow.h b/src/viewers/AnsiViewWindow.h index 0e737caa8..1729eaebd 100644 --- a/src/viewers/AnsiViewWindow.h +++ b/src/viewers/AnsiViewWindow.h @@ -2,7 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later // Copyright (C) 2024 The MMapper Authors -#include "global/macros.h" +#include "../global/macros.h" #include #include diff --git a/src/viewers/TopLevelWindows.cpp b/src/viewers/TopLevelWindows.cpp index b2c3fa468..72deea9b6 100644 --- a/src/viewers/TopLevelWindows.cpp +++ b/src/viewers/TopLevelWindows.cpp @@ -3,10 +3,10 @@ #include "TopLevelWindows.h" -#include "global/ConfigConsts.h" -#include "global/thread_utils.h" -#include "global/utils.h" -#include "global/window_utils.h" +#include "../global/ConfigConsts.h" +#include "../global/thread_utils.h" +#include "../global/utils.h" +#include "../global/window_utils.h" #include #include