From: Sam Moore Date: Thu, 28 Aug 2014 23:05:41 +0000 (+0800) Subject: Careful, you may have to shade your eyes X-Git-Url: https://git.ucc.asn.au/?a=commitdiff_plain;h=35f1190c8c8036ed11180656769cf0c1cbf7c2b3;p=ipdf%2Fcode.git Careful, you may have to shade your eyes Except for all the things that don't quite work, shading works perfectly. --- diff --git a/src/bezier.cpp b/src/bezier.cpp index 1c83660..3fa16e3 100644 --- a/src/bezier.cpp +++ b/src/bezier.cpp @@ -9,6 +9,113 @@ using namespace std; namespace IPDF { +vector SolveQuadratic(const Real & a, const Real & b, const Real & c, const Real & min, const Real & max) +{ + vector roots; roots.reserve(2); + if (a == 0 && b != 0) + { + roots.push_back(-c/b); + return roots; + } + Real disc(b*b - Real(4)*a*c); + if (disc < 0) + { + return roots; + } + else if (disc == 0) + { + Real x(-b/Real(2)*a); + if (x >= min && x <= max) + roots.push_back(x); + return roots; + } + + Real x0((-b - Sqrt(b*b - Real(4)*a*c))/(Real(2)*a)); + Real x1((-b + Sqrt(b*b - Real(4)*a*c))/(Real(2)*a)); + if (x0 > x1) + { + Real tmp(x0); + x0 = x1; + x1 = tmp; + } + if (x0 >= min && x0 <= max) + roots.push_back(x0); + if (x1 >= min && x1 <= max) + roots.push_back(x1); + return roots; +} + +/** + * Finds the root (if it exists) in a monotonicly in(de)creasing segment of a Cubic + */ + +static void CubicSolveSegment(vector & roots, const Real & a, const Real & b, const Real & c, const Real & d, Real & tl, Real & tu, const Real & delta) +{ + Real l = a*tl*tl*tl + b*tl*tl + c*tl + d; + Real u = a*tu*tu*tu + b*tu*tu + c*tu + d; + if ((l < 0 && u < 0) || (l > 0 && u > 0)) + return; + + bool negative = (u < l); // lower point > 0, upper point < 0 + while (tu - tl > delta) + { + Real t(tu+tl); + t /= 2; + Real m = a*t*t*t + b*t*t + c*t + d; + if (m > 0) + { + if (negative) + tl = t; + else + tu = t; + } + else if (negative) + { + tu = t; + } + else + { + tl = t; + } + //Debug("Delta is %f (%f - %f -> %f)", tu-tl, tu, tl, t); + } + roots.push_back(tl); +} +vector SolveCubic(const Real & a, const Real & b, const Real & c, const Real & d, const Real & min, const Real & max, const Real & delta) +{ + vector roots; roots.reserve(3); + Real tu(max); + Real tl(min); + vector turns(SolveQuadratic(a*3, b*2, c)); + //Debug("%u turning points", turns.size()); + for (unsigned i = 1; i < turns.size(); ++i) + { + tu = turns[i]; + CubicSolveSegment(roots, a, b, c, d, tl, tu,delta); + tl = turns[i]; + } + tu = max; + CubicSolveSegment(roots, a, b, c, d, tl, tu,delta); + return roots; + /* + Real maxi(100); + Real prevRes(d); + for(int i = 0; i <= 100; ++i) + { + Real x(i); + x /= maxi; + Real y = a*(x*x*x) + b*(x*x) + c*x + d; + if (((y < Real(0)) && (prevRes > Real(0))) || ((y > Real(0)) && (prevRes < Real(0)))) + { + //Debug("Found root of %fx^3 + %fx^2 + %fx + %f at %f (%f)", a, b, c, d, x, y); + roots.push_back(x); + } + prevRes = y; + } + return roots; + */ +} + /** * Factorial * Use dynamic programming / recursion @@ -77,12 +184,14 @@ pair BezierTurningPoints(const Real & p0, const Real & p1, const Rea //Debug("No real roots"); return pair(0,1); } - pair tsols = SolveQuadratic(a, b, c); - if (tsols.first > 1) tsols.first = 1; - if (tsols.first < 0) tsols.first = 0; - if (tsols.second > 1) tsols.second = 1; - if (tsols.second < 0) tsols.second = 0; - return tsols; + vector tsols = SolveQuadratic(a, b, c); + if (tsols.size() == 1) + return pair(tsols[0], tsols[0]); + else if (tsols.size() == 0) + return pair(0,1); + + return pair(tsols[0], tsols[1]); + } inline bool CompRealByPtr(const Real * a, const Real * b) @@ -239,6 +348,47 @@ pair Bezier::GetRight() const return result; } +vector Bezier::SolveXParam(const Real & x) const +{ + Real d(x0 - x); + Real c((x1 - x0)*Real(3)); + Real b((x2 - x1)*Real(3) - c); + Real a(x3 -x0 - c - b); + vector results(SolveCubic(a, b, c, d)); + for (unsigned i = 0; i < results.size(); ++i) + { + Vec2 p; + Evaluate(p.x, p.y, results[i]); + } + return results; +} + + +vector Bezier::SolveYParam(const Real & y) const +{ + Real d(y0 - y); + Real c((y1 - y0)*Real(3)); + Real b((y2 - y1)*Real(3) - c); + Real a(y3 -y0 - c - b); + vector results(SolveCubic(a, b, c, d)); + for (unsigned i = 0; i < results.size(); ++i) + { + Vec2 p; + Evaluate(p.x, p.y, results[i]); + } + return results; +} + +vector Bezier::Evaluate(const vector & u) const +{ + vector result(u.size()); + for (unsigned i = 0; i < u.size(); ++i) + { + Evaluate(result[i].x, result[i].y, u[i]); + } + return result; +} + /** * Get Bounds Rectangle of Bezier */ diff --git a/src/bezier.h b/src/bezier.h index 9dd38c0..7ff4f87 100644 --- a/src/bezier.h +++ b/src/bezier.h @@ -13,65 +13,9 @@ namespace IPDF extern Real Bernstein(int k, int n, const Real & u); extern std::pair BezierTurningPoints(const Real & p0, const Real & p1, const Real & p2, const Real & p3); - inline std::pair SolveQuadratic(const Real & a, const Real & b, const Real & c) - { - Real x0((-b + Sqrt(b*b - Real(4)*a*c))/(Real(2)*a)); - Real x1((-b - Sqrt(b*b - Real(4)*a*c))/(Real(2)*a)); - return std::pair(x0,x1); - } - - inline std::vector SolveCubic(const Real & a, const Real & b, const Real & c, const Real & d) - { - // This is going to be a big one... - // See http://en.wikipedia.org/wiki/Cubic_function#General_formula_for_roots - - std::vector roots; - // delta = 18abcd - 4 b^3 d + b^2 c^2 - 4ac^3 - 27 a^2 d^2 - -#if 0 - Real discriminant = Real(18) * a * b * c * d - Real(4) * (b * b * b) * d - + (b * b) * (c * c) - Real(4) * a * (c * c * c) - - Real(27) * (a * a) * (d * d); - - Debug("Trying to solve %fx^3 + %fx^2 + %fx + %f (Discriminant: %f)", a,b,c,d, discriminant); - // discriminant > 0 => 3 distinct, real roots. - // discriminant = 0 => a multiple root (1 or 2 real roots) - // discriminant < 0 => 1 real root, 2 complex conjugate roots - - Real delta0 = (b*b) - Real(3) * a * c; - Real delta1 = Real(2) * (b * b * b) - Real(9) * a * b * c + Real(27) * (a * a) * d; + extern std::vector SolveQuadratic(const Real & a, const Real & b, const Real & c, const Real & min = 0, const Real & max = 1); - - Real C = pow((delta1 + Sqrt((delta1 * delta1) - 4 * (delta0 * delta0 * delta0)) ) / Real(2), 1/3); - - if (false && discriminant < 0) - { - Real real_root = (Real(-1) / (Real(3) * a)) * (b + C + delta0 / C); - - roots.push_back(real_root); - - return roots; - - } -#endif - ////HACK: We know any roots we care about will be between 0 and 1, so... - Real maxi(100); - Real prevRes(d); - for(int i = 0; i <= 100; ++i) - { - Real x(i); - x /= maxi; - Real y = a*(x*x*x) + b*(x*x) + c*x + d; - if (((y < Real(0)) && (prevRes > Real(0))) || ((y > Real(0)) && (prevRes < Real(0)))) - { - Debug("Found root of %fx^3 + %fx^2 + %fx + %f at %f (%f)", a, b, c, d, x, y); - roots.push_back(x); - } - prevRes = y; - } - return roots; - - } + extern std::vector SolveCubic(const Real & a, const Real & b, const Real & c, const Real & d, const Real & min = 0, const Real & max = 1, const Real & delta = 1e-4); /** A _cubic_ bezier. **/ struct Bezier @@ -327,6 +271,21 @@ namespace IPDF x = x0*coeff[0] + x1*coeff[1] + x2*coeff[2] + x3*coeff[3]; y = y0*coeff[0] + y1*coeff[1] + y2*coeff[2] + y3*coeff[3]; } + std::vector Evaluate(const std::vector & u) const; + + std::vector SolveXParam(const Real & x) const; + std::vector SolveYParam(const Real & x) const; + + // Get points with same X + inline std::vector SolveX(const Real & x) const + { + return Evaluate(SolveXParam(x)); + } + // Get points with same Y + inline std::vector SolveY(const Real & y) const + { + return Evaluate(SolveYParam(y)); + } bool operator==(const Bezier & equ) const { diff --git a/src/document.cpp b/src/document.cpp index 1383906..5ba166c 100644 --- a/src/document.cpp +++ b/src/document.cpp @@ -313,9 +313,9 @@ unsigned Document::AddBezier(const Bezier & bezier) Bezier data = bezier.ToRelative(bounds); // Relative if (data.ToAbsolute(bounds) != bezier) { - Error("%s != %s", data.ToAbsolute(Rect(0,0,1,1)).Str().c_str(), + Warn("%s != %s", data.ToAbsolute(Rect(0,0,1,1)).Str().c_str(), bezier.Str().c_str()); - Fatal("ToAbsolute on ToRelative does not give original Bezier"); + Warn("ToAbsolute on ToRelative does not give original Bezier"); } unsigned index = AddBezierData(data); return Add(BEZIER, bounds, index); diff --git a/src/main.h b/src/main.h index 5ca71ef..88aebfe 100644 --- a/src/main.h +++ b/src/main.h @@ -94,7 +94,7 @@ inline void MainLoop(Document & doc, Screen & scr, View & view) clock_gettime(CLOCK_MONOTONIC_RAW, &real_clock_start); real_clock_now = real_clock_start; double frames = 0; - double data_rate = 1; // period between data output to stdout (if <= 0 there will be no output) + double data_rate = 0; // period between data output to stdout (if <= 0 there will be no output) uint64_t data_points = 0; setbuf(stdout, NULL); while (scr.PumpEvents()) diff --git a/src/objectrenderer.cpp b/src/objectrenderer.cpp index 8234547..f39bae8 100644 --- a/src/objectrenderer.cpp +++ b/src/objectrenderer.cpp @@ -6,6 +6,7 @@ #include "objectrenderer.h" #include "view.h" #include +#include using namespace std; @@ -213,13 +214,13 @@ Rect ObjectRenderer::CPURenderBounds(const Rect & bounds, const View & view, con return result; } -pair ObjectRenderer::CPUPointLocation(const pair & point, const View & view, const CPURenderTarget & target) +ObjectRenderer::PixelPoint ObjectRenderer::CPUPointLocation(const Vec2 & point, const View & view, const CPURenderTarget & target) { // hack... - Rect result = view.TransformToViewCoords(Rect(point.first, point.second,1,1)); + Rect result = view.TransformToViewCoords(Rect(point.x, point.y,1,1)); int64_t x = result.x*target.w; int64_t y = result.y*target.h; - return pair(x,y); + return PixelPoint(x,y); } @@ -263,7 +264,7 @@ void BezierRenderer::RenderUsingCPU(const Objects & objects, const View & view, for (int64_t j = 1; j <= blen; ++j) { control.Evaluate(x[j % 2],y[j % 2], invblen*j); - ObjectRenderer::RenderLineOnCPU((int64_t)Double(x[0]),(int64_t)Double(y[0]), (int64_t)Double(x[1]),(int64_t)Double(y[1]), target); + ObjectRenderer::RenderLineOnCPU((int64_t)Double(x[0]),(int64_t)Double(y[0]), (int64_t)Double(x[1]),(int64_t)Double(y[1]), target, Colour(0,0,0,!view.PerformingShading())); } /* @@ -362,35 +363,27 @@ void PathRenderer::RenderUsingCPU(const Objects & objects, const View & view, co Rect bounds(CPURenderBounds(objects.bounds[m_indexes[i]], view, target)); PixelBounds pix_bounds(bounds); - pix_bounds.x-=1; - pix_bounds.w+=2; - pix_bounds.y-=1; - pix_bounds.h+=2; const Path & path = objects.paths[objects.data_indices[m_indexes[i]]]; if (path.m_fill.a == 0 || !view.PerformingShading()) continue; - - - pair top(CPUPointLocation(path.m_top, view, target)); - pair bottom(CPUPointLocation(path.m_bottom, view, target)); - pair left(CPUPointLocation(path.m_left, view, target)); - pair right(CPUPointLocation(path.m_right, view, target)); - FloodFillOnCPU(top.first, top.second+1, pix_bounds, target, path.m_fill); - FloodFillOnCPU(bottom.first, bottom.second-1, pix_bounds, target, path.m_fill); - FloodFillOnCPU(left.first+1, left.second, pix_bounds, target, path.m_fill); - FloodFillOnCPU(right.first-1, right.second, pix_bounds, target, path.m_fill); - if (view.ShowingObjectBounds()) + for (unsigned f = 0; f < path.m_fill_points.size(); ++f) { - Colour c(0,0,1,1); - RenderLineOnCPU(top.first, top.second, bottom.first, bottom.second, target, c); - RenderLineOnCPU(left.first, left.second, right.first, right.second, target, c); - ObjectRenderer::RenderLineOnCPU(pix_bounds.x, pix_bounds.y, pix_bounds.x+pix_bounds.w, pix_bounds.y, target, c); - ObjectRenderer::RenderLineOnCPU(pix_bounds.x, pix_bounds.y+pix_bounds.h, pix_bounds.x+pix_bounds.w, pix_bounds.y+pix_bounds.h, target, c); - ObjectRenderer::RenderLineOnCPU(pix_bounds.x, pix_bounds.y, pix_bounds.x, pix_bounds.y+pix_bounds.h, target, c); - ObjectRenderer::RenderLineOnCPU(pix_bounds.x+pix_bounds.w, pix_bounds.y, pix_bounds.x+pix_bounds.w, pix_bounds.y+pix_bounds.h, target, c); + PixelPoint fill_point(CPUPointLocation(path.m_fill_points[f], view, target)); + FloodFillOnCPU(fill_point.first, fill_point.second, pix_bounds, target, path.m_fill); } + /*if (true)//(view.ShowingObjectBounds()) + { + + PixelPoint start(CPUPointLocation((path.m_top+path.m_left+path.m_right+path.m_bottom)/4, view, target)); + for (unsigned f = 0; f < path.m_fill_points.size(); ++f) + { + PixelPoint end(CPUPointLocation(path.m_fill_points[f], view, target)); + RenderLineOnCPU(start.first, start.second, end.first, end.second, target, Colour(0,0,1,1)); + } + } + */ } } @@ -494,20 +487,28 @@ void ObjectRenderer::RenderLineOnCPU(int64_t x0, int64_t y0, int64_t x1, int64_t void ObjectRenderer::FloodFillOnCPU(int64_t x, int64_t y, const PixelBounds & bounds, const CPURenderTarget & target, const Colour & fill) { - if (x < 0 || x < bounds.x || x > bounds.x+bounds.w || x >= target.w) - return; - if (y < 0 || y < bounds.y || y > bounds.y+bounds.h || y >= target.h) - return; - - if (GetColour(target, x, y) != Colour(1,1,1,1)) + if (fill == Colour(1,1,1,1)) return; + queue traverse; + traverse.push(PixelPoint(x,y)); + // now with 100% less stack overflows! + while (traverse.size() > 0) + { + PixelPoint cur(traverse.front()); + traverse.pop(); + if (cur.first < 0 || cur.first < bounds.x || cur.first >= bounds.x+bounds.w || cur.first >= target.w || + cur.second < 0 || cur.second < bounds.y || cur.second >= bounds.y+bounds.h || cur.second >= target.h) + continue; + if (GetColour(target, cur.first, cur.second) != Colour(1,1,1,1)) + continue; + SetColour(target, cur.first, cur.second, fill); - SetColour(target, x, y, fill); - FloodFillOnCPU(x-1, y, bounds, target, fill); - FloodFillOnCPU(x+1, y, bounds, target, fill); - FloodFillOnCPU(x,y-1,bounds,target,fill); - FloodFillOnCPU(x,y+1,bounds,target,fill); - + + traverse.push(PixelPoint(cur.first+1, cur.second)); + traverse.push(PixelPoint(cur.first-1, cur.second)); + traverse.push(PixelPoint(cur.first, cur.second-1)); + traverse.push(PixelPoint(cur.first, cur.second+1)); + } } } diff --git a/src/objectrenderer.h b/src/objectrenderer.h index 66daafb..e43e3b9 100644 --- a/src/objectrenderer.h +++ b/src/objectrenderer.h @@ -75,9 +75,11 @@ namespace IPDF int64_t x; int64_t y; int64_t w; int64_t h; PixelBounds(const Rect & bounds) : x(Double(bounds.x)), y(Double(bounds.y)), w(Double(bounds.w)), h(Double(bounds.h)) {} }; + + typedef std::pair PixelPoint; static Rect CPURenderBounds(const Rect & bounds, const View & view, const CPURenderTarget & target); - static std::pair CPUPointLocation(const std::pair & point, const View & view, const CPURenderTarget & target); + static PixelPoint CPUPointLocation(const Vec2 & point, const View & view, const CPURenderTarget & target); static void SaveBMP(const CPURenderTarget & target, const char * filename); diff --git a/src/path.cpp b/src/path.cpp index 050453b..e0eca4a 100644 --- a/src/path.cpp +++ b/src/path.cpp @@ -44,6 +44,49 @@ Path::Path(const Objects & objects, unsigned start, unsigned end, const Colour & ymax = (objb.y+objb.h); bottom = i; } + + // find fill points + Vec2 pt; + // left + pt = Vec2(objb.x, objb.y+objb.h/Real(2)); + if (PointInside(objects, pt)) + m_fill_points.push_back(pt); + // right + pt = Vec2(objb.x+objb.w, objb.y+objb.h/Real(2)); + if (PointInside(objects, pt)) + m_fill_points.push_back(pt); + // bottom + pt = Vec2(objb.x+objb.w/Real(2), objb.y+objb.h); + if (PointInside(objects, pt)) + m_fill_points.push_back(pt); + // top + pt = Vec2(objb.x+objb.w/Real(2), objb.y); + if (PointInside(objects, pt)) + m_fill_points.push_back(pt); + + // topleft + pt = Vec2(objb.x, objb.y); + if (PointInside(objects, pt)) + m_fill_points.push_back(pt); + // topright + pt = Vec2(objb.x+objb.w, objb.y); + if (PointInside(objects, pt)) + m_fill_points.push_back(pt); + // bottom left + pt = Vec2(objb.x, objb.y+objb.h); + if (PointInside(objects, pt)) + m_fill_points.push_back(pt); + // bottom right + pt = Vec2(objb.x+objb.w, objb.y); + if (PointInside(objects, pt)) + m_fill_points.push_back(pt); + + // mid + pt = Vec2(objb.x+objb.w/Real(2), objb.y+objb.h/Real(2)); + if (PointInside(objects, pt)) + m_fill_points.push_back(pt); + + } // Get actual turning point coords of the 4 edge case beziers @@ -51,18 +94,81 @@ Path::Path(const Objects & objects, unsigned start, unsigned end, const Colour & m_bottom = objects.beziers[objects.data_indices[bottom]].ToAbsolute(objects.bounds[bottom]).GetBottom(); m_left = objects.beziers[objects.data_indices[left]].ToAbsolute(objects.bounds[left]).GetLeft(); m_right = objects.beziers[objects.data_indices[right]].ToAbsolute(objects.bounds[right]).GetRight(); - /*Debug("Top: %f, %f", m_top.first, m_top.second); - Debug("Bottom: %f, %f", m_bottom.first, m_bottom.second); - Debug("Left: %f, %f", m_left.first, m_left.second); - Debug("Right: %f, %f", m_right.first, m_right.second); - Debug("Left - Right: %f, %f", m_right.first - m_left.first, m_right.second - m_left.second); - Debug("Top - Bottom: %f, %f", m_top.first - m_bottom.first, m_top.second - m_bottom.second); - */ + + Vec2 pt = (m_top + m_bottom)/2; + if (PointInside(objects, pt)) + m_fill_points.push_back(pt); + pt = (m_left + m_right)/2; + if (PointInside(objects, pt)) + m_fill_points.push_back(pt); + pt = (m_left + m_right + m_top + m_bottom)/4; + if (PointInside(objects, pt)) + m_fill_points.push_back(pt); + +} + + +bool Path::PointInside(const Objects & objects, const Vec2 & pt, bool debug) const +{ + vector x_ints; + vector y_ints; + for (unsigned i = m_start; i <= m_end; ++i) + { + Bezier bez(objects.beziers[objects.data_indices[i]].ToAbsolute(objects.bounds[i])); + vector xi(bez.SolveX(pt.x)); + vector yi(bez.SolveY(pt.y)); + x_ints.insert(x_ints.end(), xi.begin(), xi.end()); + y_ints.insert(y_ints.end(), yi.begin(), yi.end()); + } + //Debug("Solved for intersections"); + unsigned bigger = 0; + unsigned smaller = 0; + for (unsigned i = 0; i < x_ints.size(); ++i) + { + if (debug) + Debug("X Intersection %u at %f,%f vs %f,%f", i,x_ints[i].x, x_ints[i].y, pt.x, pt.y); + if (x_ints[i].y >= pt.y) + { + + ++bigger; + } + } + smaller = x_ints.size() - bigger; + if (debug) + { + Debug("%u horizontal, %u bigger, %u smaller", x_ints.size(), bigger, smaller); + } + if (smaller % 2 == 0 || bigger % 2 == 0) + return false; + + bigger = 0; + smaller = 0; + + for (unsigned i = 0; i < y_ints.size(); ++i) + { + if (debug) + Debug("Y Intersection %u at %f,%f vs %f,%f", i,x_ints[i].x, x_ints[i].y, pt.x, pt.y); + if (y_ints[i].x >= pt.x) + { + + ++bigger; + } + } + smaller = y_ints.size() - bigger; + if (debug) + { + Debug("%u vertical, %u bigger, %u smaller", y_ints.size(), bigger, smaller); + } + if (smaller % 2 == 0 || bigger % 2 == 0) + return false; + + + return true; } Rect Path::SolveBounds(const Objects & objects) const { - return Rect(m_left.first, m_top.second, m_right.first-m_left.first, m_bottom.second-m_top.second); + return Rect(m_left.x, m_top.y, m_right.x-m_left.x, m_bottom.y-m_top.y); } } diff --git a/src/path.h b/src/path.h index ac6956c..b08c146 100644 --- a/src/path.h +++ b/src/path.h @@ -29,16 +29,19 @@ namespace IPDF Rect SolveBounds(const Objects & objects) const; - + // Is point inside shape? + bool PointInside(const Objects & objects, const Vec2 & pt, bool debug=false) const; unsigned m_start; // First bounding Bezier index unsigned m_end; // Last (inclusive) '' '' unsigned m_index; // index into Objects array - std::pair m_top; - std::pair m_bottom; - std::pair m_left; - std::pair m_right; + Vec2 m_top; + Vec2 m_bottom; + Vec2 m_left; + Vec2 m_right; + + std::vector m_fill_points; Colour m_fill; // colour to fill with }; diff --git a/src/real.h b/src/real.h index 2be3fdd..b766558 100644 --- a/src/real.h +++ b/src/real.h @@ -88,6 +88,7 @@ namespace IPDF Real y; Vec2() : x(0), y(0) {} Vec2(Real _x, Real _y) : x(_x), y(_y) {} + Vec2(const std::pair & p) : x(p.first), y(p.second) {} bool operator==(const Vec2& other) const { return (x == other.x) && (y == other.y); } bool operator!=(const Vec2& other) const { return !(*this == other); } diff --git a/src/view.cpp b/src/view.cpp index 8f4a8a4..2caaadc 100644 --- a/src/view.cpp +++ b/src/view.cpp @@ -253,7 +253,7 @@ void View::Render(int width, int height) { m_screen.RenderPixels(0,0,width, height, m_cpu_rendering_pixels); //TODO: Make this work :( // Debug for great victory (do something similar for GPU and compare?) - ObjectRenderer::SaveBMP({m_cpu_rendering_pixels, width, height}, "cpu_rendering_last_frame.bmp"); + //ObjectRenderer::SaveBMP({m_cpu_rendering_pixels, width, height}, "cpu_rendering_last_frame.bmp"); } m_cached_display.UnBind(); // resets render target to the screen m_cached_display.Blit(); // blit FrameBuffer to screen