diff --git a/src/graphics/renderTarget.cpp b/src/graphics/renderTarget.cpp index 8226b13..2fc84d0 100644 --- a/src/graphics/renderTarget.cpp +++ b/src/graphics/renderTarget.cpp @@ -17,20 +17,41 @@ namespace sp { static sp::Font* default_font = nullptr; +static RenderTarget::LineDrawingMode line_drawing_mode = RenderTarget::LineDrawingMode::Quad; static sp::Shader* shader = nullptr; +static sp::Shader* quad_line_shader = nullptr; static unsigned int vertices_vbo = 0; static unsigned int indices_vbo = 0; +// Vertex buffers static std::vector vertex_data; static std::vector index_data; +// GL lines buffers static std::vector lines_vertex_data; static std::vector lines_index_data; +// Point buffers static std::vector points_vertex_data; static std::vector points_index_data; +// Anti-aliased quad line buffers +static std::vector quad_lines_vertex_data; +static std::vector quad_lines_index_data; + +// Circle/rect point generation buffer +static thread_local std::vector shape_points_buffer; + +// Calculate optimal point count for a circle based on its screen-space radius. +// Uses square root scaling: small circles get relatively more points per +// circumference than large circles, since large curves need less detail. +static size_t calculateCirclePointCount(float screen_radius) +{ + size_t count = static_cast(std::ceil(4.0f * std::sqrt(screen_radius))); + // Clamp between reasonable bounds + return std::max(static_cast(16), std::min(count, static_cast(360))); +} struct ImageInfo { @@ -38,6 +59,7 @@ struct ImageInfo glm::ivec2 size; Rect uv_rect; }; + static sp::AtlasTexture* atlas_texture; static std::unordered_map image_info; static std::unordered_map> atlas_glyphs; @@ -166,6 +188,30 @@ void main() { gl_FragColor = texture2D(u_texture, v_texcoords) * v_color; } +)"); + if (!quad_line_shader) + quad_line_shader = new Shader("quadlineshader", R"( +[vertex] +uniform mat3 u_projection; + +attribute vec2 a_position; +attribute vec4 a_color; + +varying vec4 v_color; + +void main() +{ + v_color = a_color; + gl_Position = vec4(u_projection * vec3(a_position, 1.0), 1.0); +} + +[fragment] +varying vec4 v_color; + +void main() +{ + gl_FragColor = v_color; +} )"); if (!vertices_vbo) { @@ -198,19 +244,29 @@ sp::Font* RenderTarget::getDefaultFont() return default_font; } +void RenderTarget::setLineDrawingMode(LineDrawingMode mode) +{ + line_drawing_mode = mode; +} + +RenderTarget::LineDrawingMode RenderTarget::getLineDrawingMode() +{ + return line_drawing_mode; +} + void RenderTarget::drawSprite(std::string_view texture, glm::vec2 center, float size, glm::u8vec4 color) { auto info = getTextureInfo(texture); if (info.texture || vertex_data.size() >= std::numeric_limits::max() - 4U) finish(); - + auto n = vertex_data.size(); index_data.insert(index_data.end(), { - uint16_t(n + 0), uint16_t(n + 1), uint16_t(n + 2), + uint16_t(n), uint16_t(n + 1), uint16_t(n + 2), uint16_t(n + 1), uint16_t(n + 3), uint16_t(n + 2), }); size *= 0.5f; - glm::vec2 offset{size / float(info.size.y) * float(info.size.x), size}; + glm::vec2 offset{size / static_cast(info.size.y) * static_cast(info.size.x), size}; vertex_data.push_back({ {center.x - offset.x, center.y - offset.y}, color, {info.uv_rect.position.x, info.uv_rect.position.y}}); @@ -239,7 +295,7 @@ void RenderTarget::drawSpriteClipped(std::string_view texture, glm::vec2 center, auto n = vertex_data.size(); index_data.insert(index_data.end(), { - uint16_t(n + 0), uint16_t(n + 1), uint16_t(n + 2), + uint16_t(n), uint16_t(n + 1), uint16_t(n + 2), uint16_t(n + 1), uint16_t(n + 3), uint16_t(n + 2), }); @@ -357,7 +413,7 @@ void RenderTarget::drawRotatedSpriteBlendAdd(std::string_view texture, glm::vec2 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } -void RenderTarget::drawLine(glm::vec2 start, glm::vec2 end, glm::u8vec4 color) +void RenderTarget::drawGLLine(glm::vec2 start, glm::vec2 end, glm::u8vec4 color) { if (lines_index_data.size() >= std::numeric_limits::max() - 2U) finish(); @@ -369,7 +425,7 @@ void RenderTarget::drawLine(glm::vec2 start, glm::vec2 end, glm::u8vec4 color) }); } -void RenderTarget::drawLine(glm::vec2 start, glm::vec2 end, glm::u8vec4 start_color, glm::u8vec4 end_color) +void RenderTarget::drawGLLine(glm::vec2 start, glm::vec2 end, glm::u8vec4 start_color, glm::u8vec4 end_color) { if (lines_index_data.size() >= std::numeric_limits::max() - 2U) finish(); @@ -381,7 +437,7 @@ void RenderTarget::drawLine(glm::vec2 start, glm::vec2 end, glm::u8vec4 start_co }); } -void RenderTarget::drawLine(const std::initializer_list& points, glm::u8vec4 color) +void RenderTarget::drawGLLine(const std::initializer_list& points, glm::u8vec4 color) { if (lines_index_data.size() >= std::numeric_limits::max() - points.size()) finish(); @@ -396,7 +452,7 @@ void RenderTarget::drawLine(const std::initializer_list& points, glm: } } -void RenderTarget::drawLine(const std::vector& points, glm::u8vec4 color) +void RenderTarget::drawGLLine(const std::vector& points, glm::u8vec4 color) { if (points.size() < 1) return; @@ -413,7 +469,7 @@ void RenderTarget::drawLine(const std::vector& points, glm::u8vec4 co } } -void RenderTarget::drawLineBlendAdd(const std::vector& points, glm::u8vec4 color) +void RenderTarget::drawGLLineBlendAdd(const std::vector& points, glm::u8vec4 color) { finish(); glBlendFunc(GL_SRC_ALPHA, GL_ONE); @@ -430,15 +486,224 @@ void RenderTarget::drawLineBlendAdd(const std::vector& points, glm::u glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } +void RenderTarget::drawGLCircleOutline(glm::vec2 center, float radius, float thickness, glm::u8vec4 color, size_t point_count) +{ + // Don't draw for invalid thickness or radius values. + if (thickness <= 0.0f || radius <= 0.0f) return; + + // Use automatic point count if 0. + size_t actual_point_count = (point_count == 0) + ? calculateCirclePointCount(radius * static_cast(physical_size.x) / virtual_size.x) + : point_count; + + // Draw multiple concentric circles in screen pixels to fill the thickness. + size_t circle_count = std::max(static_cast(1), static_cast(std::ceil(thickness))); + + for (size_t circle_idx = 0; circle_idx < circle_count; ++circle_idx) + { + float current_radius = radius - virtual_size.x / static_cast(physical_size.x) * static_cast(circle_idx); + if (current_radius <= 0.0f) break; + + shape_points_buffer.clear(); + shape_points_buffer.reserve(actual_point_count + 1); + + for (size_t idx = 0; idx <= actual_point_count; ++idx) + { + float f = static_cast(idx) / static_cast(actual_point_count) * static_cast(M_PI) * 2.0f; + shape_points_buffer.emplace_back(center + glm::vec2{std::sin(f) * current_radius, std::cos(f) * current_radius}); + } + + drawGLLine(shape_points_buffer, color); + } +} + +void RenderTarget::drawGLRectOutline(const sp::Rect& rect, glm::u8vec4 color) +{ + shape_points_buffer.clear(); + shape_points_buffer.reserve(5); + shape_points_buffer.emplace_back(rect.position); + shape_points_buffer.emplace_back(glm::vec2(rect.position.x + rect.size.x, rect.position.y)); + shape_points_buffer.emplace_back(rect.position + rect.size); + shape_points_buffer.emplace_back(glm::vec2(rect.position.x, rect.position.y + rect.size.y)); + shape_points_buffer.emplace_back(rect.position); + + drawGLLine(shape_points_buffer, color); +} + +// Generates a quad for a line segment. +static void generateLineQuad( + glm::vec2 start, glm::vec2 end, float half_width, + glm::u8vec4 start_color, glm::u8vec4 end_color, + std::vector& vertices, + std::vector& indices) +{ + glm::vec2 dir = end - start; + float len = glm::length(dir); + if (len < 0.0001f) return; + + // Normalize and calculate perpendicular offset + dir /= len; + glm::vec2 perp{-dir.y, dir.x}; + + glm::vec2 offset = perp * half_width; + + auto n = vertices.size(); + if (n >= std::numeric_limits::max() - 4U) return; + + // Four corners of the quad: + vertices.push_back({start - offset, start_color, {}}); + vertices.push_back({start + offset, start_color, {}}); + vertices.push_back({end - offset, end_color, {}}); + vertices.push_back({end + offset, end_color, {}}); + + // Quad triangles + indices.insert(indices.end(), { + uint16_t(n + 0), uint16_t(n + 1), uint16_t(n + 2), + uint16_t(n + 1), uint16_t(n + 3), uint16_t(n + 2), + }); +} + +// Generate a bevel join triangle to connect line segments. +static void generateBevelJoin(glm::vec2 joint_point, float half_width, glm::vec2 prev_dir, glm::vec2 next_dir, glm::u8vec4 color, std::vector& vertices, std::vector& indices) +{ + // Calculate perpendiculars for both segments. + glm::vec2 prev_perp{-prev_dir.y, prev_dir.x}; + glm::vec2 next_perp{-next_dir.y, next_dir.x}; + + // Determine which side needs the bevel (based on turn direction). + float cross = prev_dir.x * next_dir.y - prev_dir.y * next_dir.x; + + // Skip if segments are nearly parallel. + if (std::abs(cross) < 0.001f) return; + + // Bail if there are more vertices than we can count. + auto n = vertices.size(); + if (n >= std::numeric_limits::max() - 3U) return; + + // Left turn: bevel on the right side (negative perpendicular) + if (cross > 0) + { + vertices.push_back({joint_point, color, {}}); + vertices.push_back({joint_point - prev_perp * half_width, color, {}}); + vertices.push_back({joint_point - next_perp * half_width, color, {}}); + } + // Right turn: bevel on the left side (positive perpendicular) + else + { + vertices.push_back({joint_point, color, {}}); + vertices.push_back({joint_point + prev_perp * half_width, color, {}}); + vertices.push_back({joint_point + next_perp * half_width, color, {}}); + } + + indices.insert(indices.end(), { + uint16_t(n), uint16_t(n + 1), uint16_t(n + 2), + }); +} + +// Single-color line segment +void RenderTarget::drawLine(glm::vec2 start, glm::vec2 end, float width, glm::u8vec4 color) +{ + if (line_drawing_mode == LineDrawingMode::GL) + { + drawGLLine(start, end, color); + return; + } + drawLine(start, end, width, color, color); +} + +// Gradient line segment +void RenderTarget::drawLine(glm::vec2 start, glm::vec2 end, float width, glm::u8vec4 start_color, glm::u8vec4 end_color) +{ + if (line_drawing_mode == LineDrawingMode::GL) + { + drawGLLine(start, end, start_color, end_color); + return; + } + + if (quad_lines_vertex_data.size() >= std::numeric_limits::max() - 4U) + finish(); + + // Convert screen-space pixel width to virtual coordinate width. + float virtual_width = width * virtual_size.x / static_cast(physical_size.x); + float half_width = virtual_width * 0.5f; + + generateLineQuad(start, end, half_width, start_color, end_color, quad_lines_vertex_data, quad_lines_index_data); +} + +// Multi-segment line with bevel joins +void RenderTarget::drawLine(const std::vector& points, float width, glm::u8vec4 color) +{ + if (points.size() < 2) return; + + if (line_drawing_mode == LineDrawingMode::GL) + { + drawGLLine(points, color); + return; + } + + // Convert screen-space pixel width to virtual coordinate width. + const float virtual_width = width * virtual_size.x / static_cast(physical_size.x); + float half_width = virtual_width * 0.5f; + + // Draw each segment and add bevel joins between them. + for (size_t i = 0; i < points.size() - 1; ++i) + { + if (quad_lines_vertex_data.size() >= std::numeric_limits::max() - 7U) + finish(); + + glm::vec2 start = points[i]; + glm::vec2 end = points[i + 1]; + + // Generate the line segment quad. + generateLineQuad(start, end, half_width, color, color, quad_lines_vertex_data, quad_lines_index_data); + + // Add bevel join if there's a next segment. + if (i + 2 < points.size()) + { + glm::vec2 next_end = points[i + 2]; + + // Calculate direction vectors. + glm::vec2 curr_dir = end - start; + glm::vec2 next_dir = next_end - end; + const float curr_len = glm::length(curr_dir); + const float next_len = glm::length(next_dir); + + // Skip joining if current or next lines are of neglible size. + if (curr_len > 0.0001f && next_len > 0.0001f) + { + curr_dir /= curr_len; + next_dir /= next_len; + generateBevelJoin(end, half_width, curr_dir, next_dir, color, quad_lines_vertex_data, quad_lines_index_data); + } + } + } +} + +void RenderTarget::drawLineBlendAdd(glm::vec2 start, glm::vec2 end, float width, glm::u8vec4 color) +{ + finish(); + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + drawLine(start, end, width, color); + finish(); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); +} + +void RenderTarget::drawLineBlendAdd(const std::vector& points, float width, glm::u8vec4 color) +{ + if (points.size() < 2) return; + finish(); + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + drawLine(points, width, color); + finish(); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); +} + void RenderTarget::drawPoint(glm::vec2 position, glm::u8vec4 color) { if (points_index_data.size() >= std::numeric_limits::max() - 2U) finish(); - auto n = points_vertex_data.size(); points_vertex_data.push_back({position, color, atlas_white_pixel}); - points_index_data.insert(points_index_data.end(), { - uint16_t(n) - }); + points_index_data.insert(points_index_data.end(), {uint16_t(points_vertex_data.size())}); } void RenderTarget::drawRectColorMultiply(const sp::Rect& rect, glm::u8vec4 color) @@ -450,29 +715,33 @@ void RenderTarget::drawRectColorMultiply(const sp::Rect& rect, glm::u8vec4 color glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } -void RenderTarget::drawCircleOutline(glm::vec2 center, float radius, float thickness, glm::u8vec4 color) +void RenderTarget::drawCircleOutline(glm::vec2 center, float radius, float thickness, glm::u8vec4 color, size_t point_count) { - constexpr size_t point_count = 50; - - if (vertex_data.size() >= std::numeric_limits::max() - point_count * 2) - finish(); - - auto n = vertex_data.size(); - for(auto idx=0u; idx(M_PI) * 2.0f; - vertex_data.push_back({center + glm::vec2{std::sin(f) * radius, std::cos(f) * radius}, color, atlas_white_pixel}); - vertex_data.push_back({center + glm::vec2{std::sin(f) * (radius - thickness), std::cos(f) * (radius - thickness)}, color, atlas_white_pixel}); + drawGLCircleOutline(center, radius, thickness, color, point_count); + return; } - for(auto idx=0u; idx(physical_size.x) / virtual_size.x) + : point_count; + + shape_points_buffer.clear(); + shape_points_buffer.reserve(actual_point_count + 1); + + for (size_t idx = 0; idx <= actual_point_count; ++idx) { - auto n0 = n + idx * 2; - auto n1 = n + ((idx + 1) % point_count) * 2; - index_data.insert(index_data.end(), { - uint16_t(n0 + 0), uint16_t(n0 + 1), uint16_t(n1 + 0), - uint16_t(n0 + 1), uint16_t(n1 + 1), uint16_t(n1 + 0), - }); + float f = static_cast(idx) / static_cast(actual_point_count) * static_cast(M_PI) * 2.0f; + shape_points_buffer.emplace_back(center + glm::vec2{std::sin(f) * stroke_radius, std::cos(f) * stroke_radius}); } + + drawLine(shape_points_buffer, thickness, color); } void RenderTarget::drawTiled(const sp::Rect& rect, std::string_view texture, glm::vec2 offset) @@ -499,7 +768,7 @@ void RenderTarget::drawTiled(const sp::Rect& rect, std::string_view texture, glm auto n = vertex_data.size(); index_data.insert(index_data.end(), { - uint16_t(n + 0), uint16_t(n + 1), uint16_t(n + 2), + uint16_t(n), uint16_t(n + 1), uint16_t(n + 2), uint16_t(n + 1), uint16_t(n + 3), uint16_t(n + 2), }); glm::vec2 p0 = rect.position + glm::vec2(increment.x * x, increment.y * y) - offset; @@ -584,23 +853,28 @@ void RenderTarget::drawTriangles(const std::vector& points, const std for(auto& p : points) vertex_data.push_back({p, color, atlas_white_pixel}); for(auto idx : indices) - index_data.push_back(static_cast(n + idx)); + index_data.push_back(uint16_t(n + idx)); } -void RenderTarget::fillCircle(glm::vec2 center, float radius, glm::u8vec4 color) +void RenderTarget::fillCircle(glm::vec2 center, float radius, glm::u8vec4 color, size_t point_count) { - const unsigned int point_count = 50; - - if (vertex_data.size() >= std::numeric_limits::max() - point_count) + // Calculate screen-space radius for dynamic point count. + // Use automatic point count if 0, otherwise use specified count. + // TODO: DRY with outline + size_t actual_point_count = (point_count == 0) + ? calculateCirclePointCount(radius * static_cast(physical_size.x) / virtual_size.x) + : point_count; + + if (vertex_data.size() >= std::numeric_limits::max() - actual_point_count) finish(); auto n = vertex_data.size(); - for(unsigned int idx=0; idx(M_PI) * 2.0f; + float f = static_cast(idx) / static_cast(actual_point_count) * static_cast(M_PI) * 2.0f; vertex_data.push_back({center + glm::vec2{std::sin(f) * radius, std::cos(f) * radius}, color, atlas_white_pixel}); } - for(unsigned int idx=2; idx points; - points.reserve(5); - points.emplace_back(rect.position); - points.emplace_back(glm::vec2(rect.position.x + rect.size.x, rect.position.y)); - points.emplace_back(rect.position + rect.size); - points.emplace_back(glm::vec2(rect.position.x, rect.position.y + rect.size.y)); - points.emplace_back(rect.position); - - drawLine(points, color); + if (line_drawing_mode == LineDrawingMode::GL) + { + drawGLRectOutline(rect, color); + return; + } + + // Buffer points + shape_points_buffer.clear(); + shape_points_buffer.reserve(5); + shape_points_buffer.emplace_back(rect.position); + shape_points_buffer.emplace_back(glm::vec2(rect.position.x + rect.size.x, rect.position.y)); + shape_points_buffer.emplace_back(rect.position + rect.size); + shape_points_buffer.emplace_back(glm::vec2(rect.position.x, rect.position.y + rect.size.y)); + shape_points_buffer.emplace_back(rect.position); + + drawLine(shape_points_buffer, width, color); } void RenderTarget::fillRect(const sp::Rect& rect, glm::u8vec4 color) @@ -628,17 +909,13 @@ void RenderTarget::fillRect(const sp::Rect& rect, glm::u8vec4 color) auto n = vertex_data.size(); index_data.insert(index_data.end(), { - uint16_t(n + 0), uint16_t(n + 1), uint16_t(n + 2), + uint16_t(n), uint16_t(n + 1), uint16_t(n + 2), uint16_t(n + 1), uint16_t(n + 3), uint16_t(n + 2), }); - vertex_data.push_back({ - {rect.position.x, rect.position.y}, color, atlas_white_pixel}); - vertex_data.push_back({ - {rect.position.x, rect.position.y + rect.size.y}, color, atlas_white_pixel}); - vertex_data.push_back({ - {rect.position.x + rect.size.x, rect.position.y}, color, atlas_white_pixel}); - vertex_data.push_back({ - {rect.position.x + rect.size.x, rect.position.y + rect.size.y}, color, atlas_white_pixel}); + vertex_data.push_back({{rect.position.x, rect.position.y}, color, atlas_white_pixel}); + vertex_data.push_back({{rect.position.x, rect.position.y + rect.size.y}, color, atlas_white_pixel}); + vertex_data.push_back({{rect.position.x + rect.size.x, rect.position.y}, color, atlas_white_pixel}); + vertex_data.push_back({{rect.position.x + rect.size.x, rect.position.y + rect.size.y}, color, atlas_white_pixel}); } void RenderTarget::drawTexturedQuad(std::string_view texture, @@ -653,30 +930,25 @@ void RenderTarget::drawTexturedQuad(std::string_view texture, auto n = vertex_data.size(); index_data.insert(index_data.end(), { - uint16_t(n + 0), uint16_t(n + 1), uint16_t(n + 2), + uint16_t(n), uint16_t(n + 1), uint16_t(n + 2), uint16_t(n + 1), uint16_t(n + 3), uint16_t(n + 2), }); - uv0.x = uv_rect.position.x + uv_rect.size.x * uv0.x; - uv0.y = uv_rect.position.y + uv_rect.size.y * uv0.y; - uv1.x = uv_rect.position.x + uv_rect.size.x * uv1.x; - uv1.y = uv_rect.position.y + uv_rect.size.y * uv1.y; - uv2.x = uv_rect.position.x + uv_rect.size.x * uv2.x; - uv2.y = uv_rect.position.y + uv_rect.size.y * uv2.y; - uv3.x = uv_rect.position.x + uv_rect.size.x * uv3.x; - uv3.y = uv_rect.position.y + uv_rect.size.y * uv3.y; - vertex_data.push_back({p0, color, uv0}); - vertex_data.push_back({p1, color, uv1}); - vertex_data.push_back({p3, color, uv3}); - vertex_data.push_back({p2, color, uv2}); - if (info.texture) - finish(info.texture); + vertex_data.push_back({p0, color, + {uv_rect.position.x + uv_rect.size.x * uv0.x, uv_rect.position.y + uv_rect.size.y * uv0.y}}); + vertex_data.push_back({p1, color, + {uv_rect.position.x + uv_rect.size.x * uv1.x, uv_rect.position.y + uv_rect.size.y * uv1.y}}); + vertex_data.push_back({p3, color, + {uv_rect.position.x + uv_rect.size.x * uv3.x, uv_rect.position.y + uv_rect.size.y * uv3.y}}); + vertex_data.push_back({p2, color, + {uv_rect.position.x + uv_rect.size.x * uv2.x, uv_rect.position.y + uv_rect.size.y * uv2.y}}); + + if (info.texture) finish(info.texture); } void RenderTarget::drawText(sp::Rect rect, std::string_view text, Alignment align, float font_size, sp::Font* font, glm::u8vec4 color, int flags) { - if (!font) - font = default_font; + if (!font) font = default_font; auto prepared = font->prepare(text, 32, font_size, color, rect.size, align, flags); drawText(rect, prepared, flags); } @@ -1222,11 +1494,42 @@ void RenderTarget::applyBuffer(sp::Texture* texture, std::vector &da void RenderTarget::finish(sp::Texture* texture) { - applyBuffer(texture, vertex_data, index_data, GL_TRIANGLES); applyBuffer(texture, lines_vertex_data, lines_index_data, GL_LINES); applyBuffer(texture, points_vertex_data, points_index_data, GL_POINTS); - + + // Draw quad-based wide lines + if (quad_lines_vertex_data.size()) + { + quad_line_shader->bind(); + + glm::mat3 project_matrix{1.0f}; + project_matrix[0][0] = 2.0f / static_cast(virtual_size.x); + project_matrix[1][1] = -2.0f / static_cast(virtual_size.y); + project_matrix[2][0] = -1.0f; + project_matrix[2][1] = 1.0f; + glUniformMatrix3fv(quad_line_shader->getUniformLocation("u_projection"), 1, GL_FALSE, glm::value_ptr(project_matrix)); + + glBindBuffer(GL_ARRAY_BUFFER, vertices_vbo); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indices_vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(VertexData) * quad_lines_vertex_data.size(), quad_lines_vertex_data.data(), GL_DYNAMIC_DRAW); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(uint16_t) * quad_lines_index_data.size(), quad_lines_index_data.data(), GL_DYNAMIC_DRAW); + + glVertexAttribPointer(quad_line_shader->getAttributeLocation("a_position"), 2, GL_FLOAT, GL_FALSE, static_cast(sizeof(VertexData)), (void*)0); + glEnableVertexAttribArray(quad_line_shader->getAttributeLocation("a_position")); + glVertexAttribPointer(quad_line_shader->getAttributeLocation("a_color"), 4, GL_UNSIGNED_BYTE, GL_TRUE, static_cast(sizeof(VertexData)), (void*)offsetof(VertexData, color)); + glEnableVertexAttribArray(quad_line_shader->getAttributeLocation("a_color")); + + glDrawElements(GL_TRIANGLES, static_cast(quad_lines_index_data.size()), GL_UNSIGNED_SHORT, nullptr); + + quad_lines_vertex_data.clear(); + quad_lines_index_data.clear(); + + // Re-bind the standard shader for subsequent operations + shader->bind(); + glUniformMatrix3fv(shader->getUniformLocation("u_projection"), 1, GL_FALSE, glm::value_ptr(project_matrix)); + } + glBindBuffer(GL_ARRAY_BUFFER, GL_NONE); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, GL_NONE); } diff --git a/src/graphics/renderTarget.h b/src/graphics/renderTarget.h index 9d40461..9494153 100644 --- a/src/graphics/renderTarget.h +++ b/src/graphics/renderTarget.h @@ -1,5 +1,4 @@ -#ifndef SP_GRAPHICS_RENDERTARGET_H -#define SP_GRAPHICS_RENDERTARGET_H +#pragma once #include #include @@ -31,21 +30,41 @@ class RenderTarget : sp::NonCopyable void drawRotatedSprite(std::string_view texture, glm::vec2 center, float size, float rotation, glm::u8vec4 color={255,255,255,255}); void drawRotatedSpriteBlendAdd(std::string_view texture, glm::vec2 center, float size, float rotation); - void drawLine(glm::vec2 start, glm::vec2 end, glm::u8vec4 color); - void drawLine(glm::vec2 start, glm::vec2 end, glm::u8vec4 start_color, glm::u8vec4 end_color); - void drawLine(const std::initializer_list& points, glm::u8vec4 color); - void drawLine(const std::vector& points, glm::u8vec4 color); - void drawLineBlendAdd(const std::vector& points, glm::u8vec4 color); - void drawPoint(glm::vec2 position, glm::u8vec4 color); + // Line drawing mode preference + enum class LineDrawingMode { + Quad, + GL + }; + static void setLineDrawingMode(LineDrawingMode mode); + static LineDrawingMode getLineDrawingMode(); + + // Quad-based line drawing + // Width is in screen-space pixels + void drawLine(glm::vec2 start, glm::vec2 end, float width, glm::u8vec4 color); + void drawLine(glm::vec2 start, glm::vec2 end, float width, glm::u8vec4 start_color, glm::u8vec4 end_color); + void drawLine(const std::vector& points, float width, glm::u8vec4 color); + void drawLineBlendAdd(glm::vec2 start, glm::vec2 end, float width, glm::u8vec4 color); + void drawLineBlendAdd(const std::vector& points, float width, glm::u8vec4 color); void drawRectColorMultiply(const sp::Rect& rect, glm::u8vec4 color); - void drawCircleOutline(glm::vec2 center, float radius, float thickness, glm::u8vec4 color); + // Circle point_count=0 uses automatic calculation based on points per pixel + void drawCircleOutline(glm::vec2 center, float radius, float thickness, glm::u8vec4 color, size_t point_count = 0); + void fillCircle(glm::vec2 center, float radius, glm::u8vec4 color, size_t point_count = 0); void drawTiled(const sp::Rect& rect, std::string_view texture, glm::vec2 offset={0,0}); + void drawRectOutline(const sp::Rect& rect, float width, glm::u8vec4 color); + void fillRect(const sp::Rect& rect, glm::u8vec4 color); + void drawPoint(glm::vec2 position, glm::u8vec4 color); void drawTriangleStrip(const std::initializer_list& points, glm::u8vec4 color); void drawTriangleStrip(const std::vector& points, glm::u8vec4 color); void drawTriangles(const std::vector& points, const std::vector& indices, glm::u8vec4 color); - void fillCircle(glm::vec2 center, float radius, glm::u8vec4 color); - void outlineRect(const sp::Rect& rect, glm::u8vec4 color); - void fillRect(const sp::Rect& rect, glm::u8vec4 color); + + // GL_LINES primitive drawing (deprecated: no portable line width support) + void drawGLLine(glm::vec2 start, glm::vec2 end, glm::u8vec4 color); + void drawGLLine(glm::vec2 start, glm::vec2 end, glm::u8vec4 start_color, glm::u8vec4 end_color); + void drawGLLine(const std::initializer_list& points, glm::u8vec4 color); + void drawGLLine(const std::vector& points, glm::u8vec4 color); + void drawGLLineBlendAdd(const std::vector& points, glm::u8vec4 color); + void drawGLCircleOutline(glm::vec2 center, float radius, float thickness, glm::u8vec4 color, size_t point_count = 0); + void drawGLRectOutline(const sp::Rect& rect, glm::u8vec4 color); void drawTexturedQuad(std::string_view texture, glm::vec2 p0, glm::vec2 p1, glm::vec2 p2, glm::vec2 p3, @@ -89,5 +108,3 @@ class RenderTarget : sp::NonCopyable }; } - -#endif//SP_GRAPHICS_RENDERTARGET_H diff --git a/src/windowManager.cpp b/src/windowManager.cpp index 0bf658c..22aae40 100644 --- a/src/windowManager.cpp +++ b/src/windowManager.cpp @@ -50,6 +50,22 @@ Window::Window(glm::vec2 virtual_size, Mode mode, RenderChain* render_chain, int create(); sp::initOpenGL(); + // Enable multisampling after OpenGL is initialized. + switch (fsaa) + { + case 0: + // Expected input for no FSAA. + break; + case 2: + case 4: + case 8: + // Expected inputs for FSAA. + glEnable(GL_MULTISAMPLE); + break; + default: + LOG(Warning, "FSAA must be off (0), 2x, 4x, or 8x, but ", fsaa , "x was passed and ignored."); + } + all_windows.push_back(this); } @@ -113,10 +129,25 @@ void Window::setMode(Mode new_mode) void Window::setFSAA(int new_fsaa) { - if (fsaa == new_fsaa) - return; - fsaa = new_fsaa; - create(); + if (fsaa == new_fsaa) return; + + switch (new_fsaa) + { + case 0: + case 2: + case 4: + case 8: + fsaa = new_fsaa; + break; + default: + LOG(Warning, "FSAA must be off (0), 2x, 4x, or 8x, but ", new_fsaa , "x was passed. Treating as fsaa=0."); + if (fsaa == 0) return; + fsaa = 0; + } + + // Can't apply this without recreating the OpenGL context. + // Log that a restart is required. + LOG(Warning, "FSAA changed to ", fsaa, "x. Restart required for this change to take effect."); } void Window::setTitle(string title) @@ -184,6 +215,27 @@ void Window::create() SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + // Configure multisampling for FSAA. + switch (fsaa) + { + case 0: + // Expected value for no FSAA. + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0); + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0); + break; + case 2: + case 4: + case 8: + // Expected value for FSAA. + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, fsaa); + break; + default: + LOG(Warning, "FSAA must be off (0), 2x, 4x, or 8x, but ", fsaa , "x was passed. Treating as fsaa=0."); + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0); + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0); + } + int flags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI; switch(mode) { @@ -221,6 +273,22 @@ void Window::create() SDL_GL_MakeCurrent(static_cast(window), gl_context); if (SDL_GL_SetSwapInterval(-1)) SDL_GL_SetSwapInterval(1); + + // Log FSAA status. + if (fsaa > 0) + { + int actual_buffers, actual_samples; + SDL_GL_GetAttribute(SDL_GL_MULTISAMPLEBUFFERS, &actual_buffers); + SDL_GL_GetAttribute(SDL_GL_MULTISAMPLESAMPLES, &actual_samples); + + if (actual_buffers == 0 || actual_samples == 0) + LOG(Warning, "FSAA ", fsaa, "x requested but not available on this system."); + else if (actual_samples != fsaa) + LOG(Warning, "FSAA ", fsaa, "x requested, but ", actual_samples, "x provided."); + else + LOG(Info, "FSAA ", fsaa, "x enabled."); + } + setupView(); }