| @@ -20,6 +20,8 @@ | |||
| #include <lol/engine.h> | |||
| #include "loldebug.h" | |||
| #define USE_REAL 0 | |||
| using namespace lol; | |||
| LOLFX_RESOURCE_DECLARE(11_fractal); | |||
| @@ -28,6 +30,7 @@ class Fractal : public WorldEntity | |||
| { | |||
| public: | |||
| Fractal(ivec2 const &size) | |||
| : m_julia(false) | |||
| { | |||
| /* Ensure texture size is a multiple of 16 for better aligned | |||
| * data access. Store the dimensions of a texel for our shader, | |||
| @@ -41,7 +44,8 @@ public: | |||
| m_controller = new Controller("Default"); | |||
| m_profile << InputProfile::MouseKey(0, "Left") | |||
| << InputProfile::MouseKey(1, "Right") | |||
| << InputProfile::MouseKey(2, "Middle"); | |||
| << InputProfile::MouseKey(2, "Middle") | |||
| << InputProfile::Keyboard(3, "Space"); | |||
| m_controller->Init(m_profile); | |||
| m_mouse = InputDevice::GetMouse(); | |||
| @@ -70,10 +74,10 @@ public: | |||
| m_deltascale[i] = real("1"); | |||
| m_dirty[i] = 2; | |||
| } | |||
| m_center = rcmplx(-0.75, 0.0); | |||
| m_view.center = rcmplx(-0.75, 0.0); | |||
| m_zoom_speed = 0.0; | |||
| m_translate = rcmplx(0.0, 0.0); | |||
| m_radius = 5.0; | |||
| m_view.translate = rcmplx(0.0, 0.0); | |||
| m_view.radius = 5.0; | |||
| m_ready = false; | |||
| m_drag = false; | |||
| @@ -81,9 +85,9 @@ public: | |||
| { | |||
| double f = (double)i / PALETTE_STEP; | |||
| vec3 hsv(lol::fmod(i * 0.002f, 1.f), | |||
| 0.4 * lol::sin(f * 0.27 + 2.0) + 0.4, | |||
| 0.4 * lol::sin(f * 0.21 - 2.6) + 0.4); | |||
| vec3 hsv(lol::fmod(i * 0.001f, 1.f), | |||
| 0.3 * lol::sin(f * 0.27 + 2.0) + 0.3, | |||
| 0.3 * lol::sin(f * 0.21 - 2.6) + 0.6); | |||
| vec3 rgb = Color::HSVToRGB(hsv); | |||
| if (f < 7.0) | |||
| @@ -102,17 +106,17 @@ public: | |||
| } | |||
| #if !defined __native_client__ | |||
| m_zoomtext = new Text("", "data/font/ascii.png"); | |||
| m_zoomtext->SetPos(vec3(5, (float)m_window_size.y - 15, 1)); | |||
| Ticker::Ref(m_zoomtext); | |||
| m_centertext = new Text("", "data/font/ascii.png"); | |||
| m_centertext->SetPos(vec3(5, (float)m_window_size.y - 15, 1)); | |||
| m_centertext->SetPos(vec3(5, (float)m_window_size.y - 29, 1)); | |||
| Ticker::Ref(m_centertext); | |||
| m_mousetext = new Text("", "data/font/ascii.png"); | |||
| m_mousetext->SetPos(vec3(5, (float)m_window_size.y - 29, 1)); | |||
| m_mousetext->SetPos(vec3(5, (float)m_window_size.y - 43, 1)); | |||
| Ticker::Ref(m_mousetext); | |||
| m_zoomtext = new Text("", "data/font/ascii.png"); | |||
| m_zoomtext->SetPos(vec3(5, (float)m_window_size.y - 43, 1)); | |||
| Ticker::Ref(m_zoomtext); | |||
| #endif | |||
| m_position = vec3::zero; | |||
| @@ -139,7 +143,6 @@ public: | |||
| m_donequeue.pop(); | |||
| #endif | |||
| //Input::UntrackMouse(this); | |||
| #if !defined __native_client__ | |||
| Ticker::Unref(m_centertext); | |||
| Ticker::Unref(m_mousetext); | |||
| @@ -147,20 +150,20 @@ public: | |||
| #endif | |||
| } | |||
| inline dcmplx TexelToWorldOffset(vec2 texel) | |||
| inline f128cmplx TexelToWorldOffset(vec2 texel) | |||
| { | |||
| double dx = (0.5 + texel.x - m_size.x / 2) * m_texel2world.x; | |||
| double dy = (0.5 + m_size.y / 2 - texel.y) * m_texel2world.y; | |||
| return m_radius * dcmplx(dx, dy); | |||
| return m_view.radius * f128cmplx(dx, dy); | |||
| } | |||
| inline dcmplx ScreenToWorldOffset(vec2 pixel) | |||
| inline f128cmplx ScreenToWorldOffset(vec2 pixel) | |||
| { | |||
| /* No 0.5 offset here, because we want to be able to position the | |||
| * mouse at (0,0) exactly. */ | |||
| double dx = pixel.x - m_window_size.x / 2; | |||
| double dy = m_window_size.y / 2 - pixel.y; | |||
| return m_radius * m_window2world * dcmplx(dx, dy); | |||
| return m_view.radius * m_window2world * f128cmplx(dx, dy); | |||
| } | |||
| virtual void TickGame(float seconds) | |||
| @@ -172,8 +175,23 @@ public: | |||
| int prev_frame = (m_frame + 4) % 4; | |||
| m_frame = (m_frame + 1) % 4; | |||
| rcmplx worldmouse = m_center | |||
| + rcmplx(ScreenToWorldOffset((vec2)mousepos)); | |||
| if (m_controller->WasKeyPressedThisFrame(3)) | |||
| { | |||
| m_julia = !m_julia; | |||
| if (m_julia) | |||
| { | |||
| m_saved_view = m_view; | |||
| m_view.r0 = m_view.center + rcmplx(ScreenToWorldOffset((vec2)mousepos)); | |||
| } | |||
| else | |||
| { | |||
| m_view = m_saved_view; | |||
| } | |||
| for (auto & flag : m_dirty) | |||
| flag = 2; | |||
| } | |||
| rcmplx worldmouse = m_view.center + rcmplx(ScreenToWorldOffset((vec2)mousepos)); | |||
| if (m_controller->IsKeyPressed(2)) | |||
| { | |||
| @@ -182,24 +200,24 @@ public: | |||
| m_oldmouse = mousepos; | |||
| m_drag = true; | |||
| } | |||
| m_translate = rcmplx(ScreenToWorldOffset((vec2)m_oldmouse) | |||
| - ScreenToWorldOffset((vec2)mousepos)); | |||
| m_view.translate = rcmplx(ScreenToWorldOffset((vec2)m_oldmouse) | |||
| - ScreenToWorldOffset((vec2)mousepos)); | |||
| /* XXX: the purpose of this hack is to avoid translating by | |||
| * an exact number of pixels. If this were to happen, the step() | |||
| * optimisation for i915 cards in our shader would behave | |||
| * incorrectly because a quarter of the pixels in the image | |||
| * would have tied rankings in the distance calculation. */ | |||
| m_translate *= real(1023.0 / 1024.0); | |||
| m_view.translate *= real(1023.0 / 1024.0); | |||
| m_oldmouse = mousepos; | |||
| } | |||
| else | |||
| { | |||
| m_drag = false; | |||
| if (m_translate != rcmplx(0.0, 0.0)) | |||
| if (m_view.translate != rcmplx(0.0, 0.0)) | |||
| { | |||
| m_translate *= real(std::pow(2.0, -seconds * 5.0)); | |||
| if ((double)norm(m_translate) < m_radius * 1e-4) | |||
| m_translate = rcmplx(0.0, 0.0); | |||
| m_view.translate *= real(std::pow(2.0, -seconds * 5.0)); | |||
| if ((double)norm(m_view.translate) < m_view.radius * 1e-4) | |||
| m_view.translate = rcmplx(0.0, 0.0); | |||
| } | |||
| } | |||
| @@ -219,34 +237,35 @@ public: | |||
| m_zoom_speed = 0.0; | |||
| } | |||
| if (m_zoom_speed || m_translate != rcmplx(0.0, 0.0)) | |||
| if (m_zoom_speed || m_view.translate != rcmplx(0.0, 0.0)) | |||
| { | |||
| rcmplx oldcenter = m_center; | |||
| double oldradius = m_radius; | |||
| rcmplx oldcenter = m_view.center; | |||
| double oldradius = m_view.radius; | |||
| double zoom = std::pow(2.0, seconds * 1e3f * m_zoom_speed); | |||
| if (m_radius * zoom > 8.0) | |||
| if (m_view.radius * zoom > 8.0) | |||
| { | |||
| m_zoom_speed *= -1.0; | |||
| zoom = 8.0 / m_radius; | |||
| zoom = 8.0 / m_view.radius; | |||
| } | |||
| else if (m_radius * zoom < 1e-14) | |||
| else if (m_view.radius * zoom < MAX_ZOOM) | |||
| { | |||
| m_zoom_speed *= -1.0; | |||
| zoom = 1e-14 / m_radius; | |||
| zoom = MAX_ZOOM / m_view.radius; | |||
| } | |||
| m_radius *= zoom; | |||
| m_center += m_translate; | |||
| m_center = (m_center - worldmouse) * real(zoom) + worldmouse; | |||
| worldmouse = m_center | |||
| + rcmplx(ScreenToWorldOffset((vec2)mousepos)); | |||
| m_view.radius *= zoom; | |||
| m_view.center += m_view.translate; | |||
| m_view.center = (m_view.center - worldmouse) * real(zoom) + worldmouse; | |||
| worldmouse = m_view.center | |||
| + rcmplx(ScreenToWorldOffset((vec2)mousepos)); | |||
| /* Store the transformation properties to go from m_frame - 1 | |||
| * to m_frame. */ | |||
| m_deltashift[prev_frame] = (m_center - oldcenter) / real(oldradius); | |||
| m_deltashift[prev_frame] = (m_view.center - oldcenter) / real(oldradius); | |||
| m_deltashift[prev_frame].x /= m_size.x * m_texel2world.x; | |||
| m_deltashift[prev_frame].y /= m_size.y * m_texel2world.y; | |||
| m_deltascale[prev_frame] = m_radius / oldradius; | |||
| m_dirty[0] = m_dirty[1] = m_dirty[2] = m_dirty[3] = 2; | |||
| m_deltascale[prev_frame] = m_view.radius / oldradius; | |||
| for (auto & flag : m_dirty) | |||
| flag = 2; | |||
| } | |||
| else | |||
| { | |||
| @@ -283,16 +302,16 @@ public: | |||
| #if !defined __native_client__ | |||
| char buf[256]; | |||
| std::sprintf(buf, "center: "); | |||
| m_center.x.sprintf(buf + strlen(buf), 30); | |||
| m_view.center.x.sprintf(buf + strlen(buf), 30); | |||
| std::sprintf(buf + strlen(buf), " "); | |||
| m_center.y.sprintf(buf + strlen(buf), 30); | |||
| m_view.center.y.sprintf(buf + strlen(buf), 30); | |||
| m_centertext->SetText(buf); | |||
| std::sprintf(buf, " mouse: "); | |||
| worldmouse.x.sprintf(buf + strlen(buf), 30); | |||
| std::sprintf(buf + strlen(buf), " "); | |||
| worldmouse.y.sprintf(buf + strlen(buf), 30); | |||
| m_mousetext->SetText(buf); | |||
| std::sprintf(buf, " zoom: %g", 1.0 / m_radius); | |||
| std::sprintf(buf, "[%s] zoom: %g", m_julia ? "Julia" : "Mandelbrot", 1.0 / m_view.radius); | |||
| m_zoomtext->SetText(buf); | |||
| #endif | |||
| @@ -339,38 +358,49 @@ public: | |||
| u8vec4 *pixelstart = &m_pixels[0] | |||
| + m_size.x * (m_size.y / 4 * m_frame + line / 4); | |||
| dcmplx c = (dcmplx)m_center; | |||
| #if USE_REAL | |||
| rcmplx c = (rcmplx)m_view.center; | |||
| rcmplx jr0 = (rcmplx)m_view.r0; | |||
| #else | |||
| f128cmplx c = (f128cmplx)m_view.center; | |||
| f128cmplx jr0 = (f128cmplx)m_view.r0; | |||
| #endif | |||
| for (int j = jmin; j < jmax; j += 2) | |||
| for (int i = m_frame % 2; i < m_size.x; i += 2) | |||
| { | |||
| double xr, yr, x0, y0, x1, y1, x2, y2, x3, y3; | |||
| dcmplx z0 = c + TexelToWorldOffset(vec2(ivec2(i, j))); | |||
| //dcmplx r0(0.28693186889504513, 0.014286693904085048); | |||
| //dcmplx r0(0.001643721971153, 0.822467633298876); | |||
| //dcmplx r0(-1.207205434596, 0.315432814901); | |||
| //dcmplx r0(-0.79192956889854, -0.14632423080102); | |||
| //dcmplx r0(0.3245046418497685, 0.04855101129280834); | |||
| dcmplx r0 = z0; | |||
| #if USE_REAL | |||
| real xr, yr, x0, y0, x1, y1, x2, y2, x3, y3; | |||
| real sqx0, sqy0, sqx1, sqy1, sqx2, sqy2, sqx3, sqy3; | |||
| rcmplx z0 = c + rcmplx(TexelToWorldOffset(vec2(ivec2(i, j)))); | |||
| rcmplx r0 = m_julia ? jr0 : z0; | |||
| #else | |||
| ldouble xr, yr, x0, y0, x1, y1, x2, y2, x3, y3; | |||
| ldouble sqx0, sqy0, sqx1, sqy1, sqx2, sqy2, sqx3, sqy3; | |||
| f128cmplx z0 = c + TexelToWorldOffset(vec2(ivec2(i, j))); | |||
| f128cmplx r0 = m_julia ? jr0 : z0; | |||
| #endif | |||
| x0 = z0.x; y0 = z0.y; | |||
| xr = r0.x; yr = r0.y; | |||
| sqx0 = x0 * x0; sqy0 = y0 * y0; | |||
| int iter = MAX_ITERATIONS - 4; | |||
| for (;;) | |||
| { | |||
| /* Unroll the loop: tests are more expensive to do at each | |||
| * iteration than the few extra multiplications. */ | |||
| x1 = x0 * x0 - y0 * y0 + xr; | |||
| y1 = x0 * y0 + x0 * y0 + yr; | |||
| x2 = x1 * x1 - y1 * y1 + xr; | |||
| y2 = x1 * y1 + x1 * y1 + yr; | |||
| x3 = x2 * x2 - y2 * y2 + xr; | |||
| y3 = x2 * y2 + x2 * y2 + yr; | |||
| x0 = x3 * x3 - y3 * y3 + xr; | |||
| y0 = x3 * y3 + x3 * y3 + yr; | |||
| if (x0 * x0 + y0 * y0 >= maxsqlen) | |||
| * iteration than the few extra multiplications, at least | |||
| * with floats/doubles. */ | |||
| x1 = sqx0 - sqy0 + xr; y1 = x0 * y0 + x0 * y0 + yr; | |||
| sqx1 = x1 * x1; sqy1 = y1 * y1; | |||
| x2 = sqx1 - sqy1 + xr; y2 = x1 * y1 + x1 * y1 + yr; | |||
| sqx2 = x2 * x2; sqy2 = y2 * y2; | |||
| x3 = sqx2 - sqy2 + xr; y3 = x2 * y2 + x2 * y2 + yr; | |||
| sqx3 = x3 * x3; sqy3 = y3 * y3; | |||
| x0 = sqx3 - sqy3 + xr; y0 = x3 * y3 + x3 * y3 + yr; | |||
| sqx0 = x0 * x0; sqy0 = y0 * y0; | |||
| if ((double)sqx0 + (double)sqy0 >= maxsqlen) | |||
| break; | |||
| iter -= 4; | |||
| if (iter < 4) | |||
| @@ -379,19 +409,19 @@ public: | |||
| if (iter) | |||
| { | |||
| double n = x0 * x0 + y0 * y0; | |||
| double n = (double)sqx0 + (double)sqy0; | |||
| if (x1 * x1 + y1 * y1 >= maxsqlen) | |||
| if ((double)sqx1 + (double)sqy1 >= maxsqlen) | |||
| { | |||
| iter += 3; n = x1 * x1 + y1 * y1; | |||
| iter += 3; n = (double)sqx1 + (double)sqy1; | |||
| } | |||
| else if (x2 * x2 + y2 * y2 >= maxsqlen) | |||
| else if ((double)sqx2 + (double)sqy2 >= maxsqlen) | |||
| { | |||
| iter += 2; n = x2 * x2 + y2 * y2; | |||
| iter += 2; n = (double)sqx2 + (double)sqy2; | |||
| } | |||
| else if (x3 * x3 + y3 * y3 >= maxsqlen) | |||
| else if ((double)sqx3 + (double)sqy3 >= maxsqlen) | |||
| { | |||
| iter += 1; n = x3 * x3 + y3 * y3; | |||
| iter += 1; n = (double)sqx3 + (double)sqy3; | |||
| } | |||
| if (n > maxsqlen * maxsqlen) | |||
| @@ -399,7 +429,7 @@ public: | |||
| /* Approximate log(sqrt(n))/log(sqrt(maxsqlen)) */ | |||
| double f = iter; | |||
| union { double n; uint64_t x; } u = { n }; | |||
| union { double n; uint64_t x; } u = { (double)n }; | |||
| double k = (double)(u.x >> 42) - (((1 << 10) - 1) << 10); | |||
| k *= k1; | |||
| @@ -509,11 +539,14 @@ public: | |||
| } | |||
| private: | |||
| static int const MAX_ITERATIONS = 340; | |||
| static int const MAX_ITERATIONS = 400; | |||
| static int const PALETTE_STEP = 32; | |||
| static int const MAX_THREADS = 8; | |||
| static int const MAX_LINES = 8; | |||
| // 1e-14 for doubles, 1e-17 for long doubles | |||
| static double constexpr MAX_ZOOM = 1e-17; | |||
| ivec2 m_size, m_window_size, m_oldmouse; | |||
| double m_window2world; | |||
| dvec2 m_texel2world; | |||
| @@ -530,9 +563,18 @@ private: | |||
| int m_frame, m_slices, m_dirty[4]; | |||
| bool m_ready, m_drag; | |||
| rcmplx m_deltashift[4], m_center, m_translate; | |||
| struct view_settings | |||
| { | |||
| rcmplx center, translate, r0; | |||
| double radius; | |||
| }; | |||
| view_settings m_view, m_saved_view; | |||
| rcmplx m_deltashift[4]; | |||
| real m_deltascale[4]; | |||
| double m_zoom_speed, m_radius; | |||
| double m_zoom_speed; | |||
| bool m_julia; | |||
| vec4 m_texel_settings, m_screen_settings; | |||
| mat4 m_zoom_settings; | |||