From 8ab3b2d77aae6eb2f47487fef326bcbfeebc0951 Mon Sep 17 00:00:00 2001 From: Vankata453 <78196474+Vankata453@users.noreply.github.com> Date: Fri, 25 Aug 2023 16:27:36 +0300 Subject: [PATCH] SDL sub-pixel rendering support Replaces drawing functions in `SDLPainter` with their `float` equivalents. Fixes #2422. --- src/math/rect.hpp | 5 ++ src/math/rectf.hpp | 28 ++++++- src/video/sdl/sdl_painter.cpp | 151 +++++++++++++++------------------- 3 files changed, 97 insertions(+), 87 deletions(-) diff --git a/src/math/rect.hpp b/src/math/rect.hpp index 3138a388b9e..a2cdf0c8e2f 100644 --- a/src/math/rect.hpp +++ b/src/math/rect.hpp @@ -98,6 +98,11 @@ class Rect final top <= other.top && other.bottom <= bottom); } + int get_left() const { return left; } + int get_right() const { return right; } + int get_top() const { return top; } + int get_bottom() const { return bottom; } + int get_width() const { return right - left; } int get_height() const { return bottom - top; } Size get_size() const { return Size(right - left, bottom - top); } diff --git a/src/math/rectf.hpp b/src/math/rectf.hpp index 98775645e0d..b67ccc32d99 100644 --- a/src/math/rectf.hpp +++ b/src/math/rectf.hpp @@ -20,13 +20,14 @@ #include #include +#include + #include "math/anchor_point.hpp" +#include "math/rect.hpp" #include "math/sizef.hpp" #include "math/vector.hpp" #include "util/log.hpp" -class Rect; - class Rectf final { public: @@ -69,6 +70,12 @@ class Rectf final initialize(); } + Rectf(const SDL_FRect& rect) : + m_p1(rect.x, rect.y), m_size(rect.w, rect.h) + { + initialize(); + } + Rectf(const Rect& rect); bool operator==(const Rectf& other) const @@ -125,6 +132,12 @@ class Rectf final } Sizef get_size() const { return m_size; } + bool empty() const + { + return get_width() <= 0 || + get_height() <= 0; + } + void move(const Vector& v) { m_p1 += v; } Rectf moved(const Vector& v) const { return Rectf(m_p1 + v, m_size); } @@ -179,6 +192,17 @@ class Rectf final p.y - m_p1.y); } + Rect to_rect() const + { + return { static_cast(m_p1.x), static_cast(m_p1.y), + static_cast(m_size.width), static_cast(m_size.height) }; + } + + SDL_FRect to_sdl() const + { + return { m_p1.x, m_p1.y, m_size.width, m_size.height }; + } + private: /// upper left edge Vector m_p1; diff --git a/src/video/sdl/sdl_painter.cpp b/src/video/sdl/sdl_painter.cpp index 5a9578de752..e4db9ee8803 100644 --- a/src/video/sdl/sdl_painter.cpp +++ b/src/video/sdl/sdl_painter.cpp @@ -33,23 +33,6 @@ namespace { -SDL_Rect to_sdl_rect(const Rectf& rect) -{ - SDL_Rect sdl_rect; - - // floorf() here due to int(-0.5) and int(0.5) both rounding to 0, - // thus creating a jump in coordinates at 0 - sdl_rect.x = static_cast(floorf(rect.get_left())); - sdl_rect.y = static_cast(floorf(rect.get_top())); - - // roundf() here due to int(rect.get_right()y - rect.get_left()y) being - // off-by-one due to float errors - sdl_rect.w = static_cast(roundf(rect.get_width())); - sdl_rect.h = static_cast(roundf(rect.get_height())); - - return sdl_rect; -} - SDL_BlendMode blend2sdl(const Blend& blend) { if (blend == Blend::NONE) @@ -80,41 +63,41 @@ SDL_BlendMode blend2sdl(const Blend& blend) where srcrect is outside of imgrect, some of those rects will be empty. The rectangles will be returned in the order inside, top, left, right, bottom */ -std::tuple -intersect(const Rect& srcrect, const Rect& imgrect) +std::tuple +intersect(const Rect& srcrect, const Rectf& imgrect) { return std::make_tuple( // inside - Rect(std::max(srcrect.left, imgrect.left), std::max(srcrect.top, imgrect.top), - std::min(srcrect.right, imgrect.right), std::min(srcrect.bottom, imgrect.bottom)), + Rectf(std::max(static_cast(srcrect.get_left()), imgrect.get_left()), std::max(static_cast(srcrect.get_top()), imgrect.get_top()), + std::min(static_cast(srcrect.get_right()), imgrect.get_right()), std::min(static_cast(srcrect.get_bottom()), imgrect.get_bottom())), // top - Rect(srcrect.left, srcrect.top, - srcrect.right, imgrect.top), + Rectf(static_cast(srcrect.get_left()), static_cast(srcrect.get_top()), + static_cast(srcrect.get_right()), imgrect.get_top()), // left - Rect(srcrect.left, std::max(srcrect.top, imgrect.top), - imgrect.left, std::min(srcrect.bottom, imgrect.bottom)), + Rectf(static_cast(srcrect.get_left()), std::max(static_cast(srcrect.get_top()), imgrect.get_top()), + imgrect.get_left(), std::min(static_cast(srcrect.get_bottom()), imgrect.get_bottom())), // right - Rect(imgrect.right, std::max(srcrect.top, imgrect.top), - srcrect.right, std::min(srcrect.bottom, imgrect.bottom)), + Rectf(imgrect.get_right(), std::max(static_cast(srcrect.get_top()), imgrect.get_top()), + static_cast(srcrect.get_right()), std::min(static_cast(srcrect.get_bottom()), imgrect.get_bottom())), // bottom - Rect(srcrect.left, imgrect.bottom, - srcrect.right, srcrect.bottom) + Rectf(static_cast(srcrect.get_left()), imgrect.get_bottom(), + static_cast(srcrect.get_right()), static_cast(srcrect.get_bottom())) ); } /* Map the area covered by inside in srcrect to dstrect */ -Rect relative_map(const Rect& inside, const Rect& srcrect, const Rect& dstrect) +Rectf relative_map(const Rectf& inside, const Rectf& srcrect, const Rectf& dstrect) { assert(srcrect.contains(inside)); - Rect result(dstrect.left + (inside.left - srcrect.left) * dstrect.get_width() / srcrect.get_width(), - dstrect.top + (inside.top - srcrect.top) * dstrect.get_height() / srcrect.get_height(), - dstrect.left + (inside.right - srcrect.left) * dstrect.get_width() / srcrect.get_width(), - dstrect.top + (inside.bottom - srcrect.top) * dstrect.get_height() / srcrect.get_height()); + Rectf result(dstrect.get_left() + (inside.get_left() - srcrect.get_left()) * dstrect.get_width() / srcrect.get_width(), + dstrect.get_top() + (inside.get_top() - srcrect.get_top()) * dstrect.get_height() / srcrect.get_height(), + dstrect.get_left() + (inside.get_right() - srcrect.get_left()) * dstrect.get_width() / srcrect.get_width(), + dstrect.get_top() + (inside.get_bottom() - srcrect.get_top()) * dstrect.get_height() / srcrect.get_height()); assert(dstrect.contains(result)); @@ -122,10 +105,10 @@ Rect relative_map(const Rect& inside, const Rect& srcrect, const Rect& dstrect) } void render_texture(SDL_Renderer* renderer, - SDL_Texture* texture, const Rect& imgrect, - const Rect& srcrect, const Rect& dstrect) + SDL_Texture* texture, const Rectf& imgrect, + const Rect& srcrect, const Rectf& dstrect) { - assert(imgrect.contains(srcrect.left, srcrect.top)); + assert(imgrect.contains(Vector(srcrect.get_left(), srcrect.get_top()))); if (srcrect.empty() || dstrect.empty()) return; @@ -133,22 +116,22 @@ void render_texture(SDL_Renderer* renderer, if (imgrect.contains(srcrect)) { SDL_Rect sdl_srcrect = srcrect.to_sdl(); - SDL_Rect sdl_dstrect = dstrect.to_sdl(); - SDL_RenderCopy(renderer, texture, &sdl_srcrect, &sdl_dstrect); + SDL_FRect sdl_dstrect = dstrect.to_sdl(); + SDL_RenderCopyF(renderer, texture, &sdl_srcrect, &sdl_dstrect); } else { - Rect inside; - std::array rest; + Rectf inside; + std::array rest; std::tie(inside, rest[0], rest[1], rest[2], rest[3]) = intersect(srcrect, imgrect); - render_texture(renderer, texture, imgrect, inside, relative_map(inside, srcrect, dstrect)); + render_texture(renderer, texture, imgrect, inside.to_rect(), relative_map(inside, srcrect, dstrect)); - for (const Rect& rect : rest) + for (const Rectf& rect : rest) { - const Rect new_srcrect(math::positive_mod(rect.left, imgrect.get_width()), - math::positive_mod(rect.top, imgrect.get_height()), - rect.get_size()); + const Rect new_srcrect(math::positive_mod(rect.get_left(), imgrect.get_width()), + math::positive_mod(rect.get_top(), imgrect.get_height()), + Size(static_cast(rect.get_width()), static_cast(rect.get_height()))); render_texture(renderer, texture, imgrect, new_srcrect, relative_map(rect, srcrect, dstrect)); } @@ -159,7 +142,7 @@ void render_texture(SDL_Renderer* renderer, void RenderCopyEx(SDL_Renderer* renderer, SDL_Texture* texture, const SDL_Rect* sdl_srcrect, - const SDL_Rect* sdl_dstrect, + const SDL_FRect* sdl_dstrect, const double angle, const SDL_Point* center, const SDL_RendererFlip flip, @@ -168,7 +151,7 @@ void RenderCopyEx(SDL_Renderer* renderer, Vector animate = sampler.get_animate(); if (animate.x == 0.0f && animate.y == 0.0f) { - SDL_RenderCopyEx(renderer, texture, sdl_srcrect, sdl_dstrect, angle, nullptr, flip); + SDL_RenderCopyExF(renderer, texture, sdl_srcrect, sdl_dstrect, angle, nullptr, flip); } else { @@ -198,16 +181,16 @@ void RenderCopyEx(SDL_Renderer* renderer, flip || angle != 0.0) { - SDL_RenderCopyEx(renderer, texture, sdl_srcrect, sdl_dstrect, angle, nullptr, flip); + SDL_RenderCopyExF(renderer, texture, sdl_srcrect, sdl_dstrect, angle, nullptr, flip); } else { - Rect imgrect(0, 0, Size(width, height)); + Rectf imgrect(Vector(), Sizef(static_cast(width), static_cast(height))); Rect srcrect(math::positive_mod(sdl_srcrect->x + tex_off_x, width), math::positive_mod(sdl_srcrect->y + tex_off_y, height), Size(sdl_srcrect->w, sdl_srcrect->h)); - render_texture(renderer, texture, imgrect, srcrect, Rect(*sdl_dstrect)); + render_texture(renderer, texture, imgrect, srcrect, Rectf(*sdl_dstrect)); } } } @@ -231,8 +214,8 @@ SDLPainter::draw_texture(const TextureRequest& request) for (size_t i = 0; i < request.srcrects.size(); ++i) { - const SDL_Rect& src_rect = to_sdl_rect(request.srcrects[i]); - const SDL_Rect& dst_rect = to_sdl_rect(request.dstrects[i]); + const SDL_Rect& src_rect = request.srcrects[i].to_rect().to_sdl(); + const SDL_FRect& dst_rect = request.dstrects[i].to_sdl(); Uint8 r = static_cast(request.color.red * 255); Uint8 g = static_cast(request.color.green * 255); @@ -276,21 +259,20 @@ SDLPainter::draw_gradient(const GradientRequest& request) fabsf(top.alpha - bottom.alpha))) * 255); n = std::max(n, 1); - int next_step = (direction == VERTICAL || direction == VERTICAL_SECTOR) ? - static_cast(region.get_top()) : static_cast(region.get_left()); + float next_step = (direction == VERTICAL || direction == VERTICAL_SECTOR) ? region.get_top() : region.get_left(); for (int i = 0; i < n; ++i) { - SDL_Rect rect; + SDL_FRect rect; if (direction == VERTICAL || direction == VERTICAL_SECTOR) { - rect.x = static_cast(region.get_left()); + rect.x = region.get_left(); rect.y = next_step; - rect.w = static_cast(region.get_right() - region.get_left()); - rect.h = static_cast(ceilf((region.get_bottom() - region.get_top()) / static_cast(n))); + rect.w = region.get_right() - region.get_left(); + rect.h = ceilf((region.get_bottom() - region.get_top()) / static_cast(n)); // Account for the build-up of rounding errors due to floating point precision. - if (next_step > static_cast(region.get_top() + (region.get_bottom() - region.get_top()) * static_cast(i) / static_cast(n))) + if (next_step > region.get_top() + (region.get_bottom() - region.get_top()) * static_cast(i) / static_cast(n)) --rect.h; next_step += rect.h; @@ -298,12 +280,12 @@ SDLPainter::draw_gradient(const GradientRequest& request) else { rect.x = next_step; - rect.y = static_cast(region.get_top()); - rect.w = static_cast(ceilf((region.get_right() - region.get_left()) / static_cast(n))); - rect.h = static_cast(region.get_bottom() - region.get_top()); + rect.y = region.get_top(); + rect.w = ceilf((region.get_right() - region.get_left()) / static_cast(n)); + rect.h = region.get_bottom() - region.get_top(); // Account for the build-up of rounding errors due to floating point precision. - if (next_step > static_cast(region.get_left() + (region.get_right() - region.get_left()) * static_cast(i) / static_cast(n))) + if (next_step > region.get_left() + (region.get_right() - region.get_left()) * static_cast(i) / static_cast(n)) --rect.w; next_step += rect.w; @@ -357,43 +339,42 @@ SDLPainter::draw_gradient(const GradientRequest& request) SDL_SetRenderDrawBlendMode(m_sdl_renderer, blend2sdl(request.blend)); SDL_SetRenderDrawColor(m_sdl_renderer, r, g, b, a); - SDL_RenderFillRect(m_sdl_renderer, &rect); + SDL_RenderFillRectF(m_sdl_renderer, &rect); } } void SDLPainter::draw_filled_rect(const FillRectRequest& request) { - SDL_Rect rect = to_sdl_rect(request.rect); + SDL_FRect rect = request.rect.to_sdl(); Uint8 r = static_cast(request.color.red * 255); Uint8 g = static_cast(request.color.green * 255); Uint8 b = static_cast(request.color.blue * 255); Uint8 a = static_cast(request.color.alpha * 255); - int radius = std::min(std::min(rect.h / 2, rect.w / 2), - static_cast(request.radius)); + int radius = std::min(std::min(rect.h / 2, rect.w / 2), request.radius); if (radius) { int slices = radius; // rounded top and bottom parts - std::vector rects; + std::vector rects; rects.reserve(2*slices + 1); for (int i = 0; i < slices; ++i) { float p = (static_cast(i) + 0.5f) / static_cast(slices); int xoff = radius - static_cast(sqrtf(1.0f - p * p) * static_cast(radius)); - SDL_Rect tmp; + SDL_FRect tmp; tmp.x = rect.x + xoff; tmp.y = rect.y + (radius - i); tmp.w = rect.w - 2*(xoff); tmp.h = 1; rects.push_back(tmp); - SDL_Rect tmp2; + SDL_FRect tmp2; tmp2.x = rect.x + xoff; tmp2.y = rect.y + rect.h - radius + i; tmp2.w = rect.w - 2*xoff; @@ -408,7 +389,7 @@ SDLPainter::draw_filled_rect(const FillRectRequest& request) if (2*radius < rect.h) { // center rectangle - SDL_Rect tmp; + SDL_FRect tmp; tmp.x = rect.x; tmp.y = rect.y + radius + 1; tmp.w = rect.w; @@ -418,7 +399,7 @@ SDLPainter::draw_filled_rect(const FillRectRequest& request) SDL_SetRenderDrawBlendMode(m_sdl_renderer, SDL_BLENDMODE_BLEND); SDL_SetRenderDrawColor(m_sdl_renderer, r, g, b, a); - SDL_RenderFillRects(m_sdl_renderer, &*rects.begin(), static_cast(rects.size())); + SDL_RenderFillRectsF(m_sdl_renderer, &*rects.begin(), static_cast(rects.size())); } else { @@ -426,7 +407,7 @@ SDLPainter::draw_filled_rect(const FillRectRequest& request) { SDL_SetRenderDrawBlendMode(m_sdl_renderer, SDL_BLENDMODE_BLEND); SDL_SetRenderDrawColor(m_sdl_renderer, r, g, b, a); - SDL_RenderFillRect(m_sdl_renderer, &rect); + SDL_RenderFillRectF(m_sdl_renderer, &rect); } } } @@ -443,29 +424,29 @@ SDLPainter::draw_inverse_ellipse(const InverseEllipseRequest& request) const Viewport& viewport = m_video_system.get_viewport(); const int max_slices = 256; - SDL_Rect rects[2*max_slices+2]; + SDL_FRect rects[2*max_slices+2]; int slices = std::min(static_cast(request.size.y), max_slices); for (int i = 0; i < slices; ++i) { float p = ((static_cast(i) + 0.5f) / static_cast(slices)) * 2.0f - 1.0f; int xoff = static_cast(sqrtf(1.0f - p*p) * w / 2); - SDL_Rect& left = rects[2*i+0]; - SDL_Rect& right = rects[2*i+1]; + SDL_FRect& left = rects[2*i+0]; + SDL_FRect& right = rects[2*i+1]; left.x = 0; - left.y = top + (i * static_cast(h) / slices); - left.w = static_cast(x) - xoff; - left.h = top + ((i+1) * static_cast(h) / slices) - left.y; + left.y = top + (i * h / slices); + left.w = x - xoff; + left.h = top + ((i+1) * h / slices) - left.y; - right.x = static_cast(x) + xoff; + right.x = x + xoff; right.y = left.y; right.w = viewport.get_screen_width() - right.x; right.h = left.h; } - SDL_Rect& top_rect = rects[2*slices+0]; - SDL_Rect& bottom_rect = rects[2*slices+1]; + SDL_FRect& top_rect = rects[2*slices+0]; + SDL_FRect& bottom_rect = rects[2*slices+1]; top_rect.x = 0; top_rect.y = 0; @@ -473,7 +454,7 @@ SDLPainter::draw_inverse_ellipse(const InverseEllipseRequest& request) top_rect.h = top; bottom_rect.x = 0; - bottom_rect.y = top + static_cast(h); + bottom_rect.y = top + h; bottom_rect.w = viewport.get_screen_width(); bottom_rect.h = viewport.get_screen_height() - bottom_rect.y; @@ -484,7 +465,7 @@ SDLPainter::draw_inverse_ellipse(const InverseEllipseRequest& request) SDL_SetRenderDrawBlendMode(m_sdl_renderer, SDL_BLENDMODE_BLEND); SDL_SetRenderDrawColor(m_sdl_renderer, r, g, b, a); - SDL_RenderFillRects(m_sdl_renderer, rects, 2*slices+2); + SDL_RenderFillRectsF(m_sdl_renderer, rects, 2*slices+2); } void