You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

преди 10 години
преди 10 години
преди 13 години
преди 12 години
преди 12 години
преди 10 години
преди 11 години
преди 11 години
преди 10 години
преди 10 години
преди 13 години
преди 13 години
преди 10 години
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578
  1. //
  2. // Lol Engine — Fractal tutorial
  3. //
  4. // Copyright © 2011—2015 Sam Hocevar <sam@hocevar.net>
  5. //
  6. // This program is free software. It comes without any warranty, to
  7. // the extent permitted by applicable law. You can redistribute it
  8. // and/or modify it under the terms of the Do What the Fuck You Want
  9. // to Public License, Version 2, as published by the WTFPL Task Force.
  10. // See http://www.wtfpl.net/ for more details.
  11. //
  12. #if HAVE_CONFIG_H
  13. # include "config.h"
  14. #endif
  15. #include <cstring>
  16. #include <cstdio>
  17. #include <lol/engine.h>
  18. #include "loldebug.h"
  19. using namespace lol;
  20. LOLFX_RESOURCE_DECLARE(11_fractal);
  21. class Fractal : public WorldEntity
  22. {
  23. public:
  24. Fractal(ivec2 const &size)
  25. {
  26. /* Ensure texture size is a multiple of 16 for better aligned
  27. * data access. Store the dimensions of a texel for our shader,
  28. * as well as the half-size of the screen. */
  29. m_size = size;
  30. m_size.x = (m_size.x + 15) & ~15;
  31. m_size.y = (m_size.y + 15) & ~15;
  32. m_texel_settings = vec4(1.0, 1.0, 2.0, 2.0) / (vec4)m_size.xyxy;
  33. m_screen_settings = vec4(1.0, 1.0, 0.5, 0.5) * (vec4)m_size.xyxy;
  34. /* Window size decides the world aspect ratio. For instance, 640×480
  35. * will be mapped to (-0.66,-0.5) - (0.66,0.5). */
  36. #if !defined __native_client__
  37. m_window_size = Video::GetSize();
  38. #else
  39. /* FIXME: it's illegal to call this on the game thread! */
  40. m_window_size = ivec2(640, 480);
  41. #endif
  42. if (m_window_size.y < m_window_size.x)
  43. m_window2world = 0.5 / m_window_size.y;
  44. else
  45. m_window2world = 0.5 / m_window_size.x;
  46. m_texel2world = (dvec2)m_window_size / (dvec2)m_size * m_window2world;
  47. m_oldmouse = ivec2(0, 0);
  48. m_pixels.resize(m_size.x * m_size.y);
  49. m_frame = -1;
  50. m_slices = 4;
  51. for (int i = 0; i < 4; i++)
  52. {
  53. m_deltashift[i] = real("0");
  54. m_deltascale[i] = real("1");
  55. m_dirty[i] = 2;
  56. }
  57. #if defined _XBOX
  58. //m_center = rcmplx(-.22815528839841, -1.11514249704382);
  59. //m_center = rcmplx(0.001643721971153, 0.822467633298876);
  60. m_center = rcmplx("-0.65823419062254", "0.50221777363480");
  61. m_zoom_speed = -0.025;
  62. #else
  63. m_center = rcmplx(-0.75, 0.0);
  64. m_zoom_speed = 0.0;
  65. #endif
  66. m_translate = rcmplx(0.0, 0.0);
  67. m_radius = 5.0;
  68. m_ready = false;
  69. m_drag = false;
  70. for (int i = 0; i < (MAX_ITERATIONS + 1) * PALETTE_STEP; i++)
  71. {
  72. double f = (double)i / PALETTE_STEP;
  73. double r = 0.5 * lol::sin(f * 0.27 + 2.0) + 0.5;
  74. double g = 0.5 * lol::sin(f * 0.17 - 1.8) + 0.5;
  75. double b = 0.5 * lol::sin(f * 0.21 - 2.6) + 0.5;
  76. if (f < 7.0)
  77. {
  78. f = f < 1.0 ? 0.0 : (f - 1.0) / 6.0;
  79. r *= f;
  80. g *= f;
  81. b *= f;
  82. }
  83. uint8_t red = (uint8_t)r * 256;
  84. uint8_t green = (uint8_t)g * 256;
  85. uint8_t blue = (uint8_t)b * 256;
  86. #if defined _XBOX
  87. m_palette.push(u8vec4(255, red, green, blue));
  88. #elif defined __native_client__
  89. m_palette.push(u8vec4(red, green, blue, 255));
  90. #else
  91. m_palette.push(u8vec4(blue, green, red, 255));
  92. #endif
  93. }
  94. #if !defined __native_client__
  95. m_centertext = new Text(NULL, "data/font/ascii.png");
  96. m_centertext->SetPos(vec3(5, (float)m_window_size.y - 15, 1));
  97. Ticker::Ref(m_centertext);
  98. m_mousetext = new Text(NULL, "data/font/ascii.png");
  99. m_mousetext->SetPos(vec3(5, (float)m_window_size.y - 29, 1));
  100. Ticker::Ref(m_mousetext);
  101. m_zoomtext = new Text(NULL, "data/font/ascii.png");
  102. m_zoomtext->SetPos(vec3(5, (float)m_window_size.y - 43, 1));
  103. Ticker::Ref(m_zoomtext);
  104. #endif
  105. m_position = vec3::zero;
  106. m_aabb.aa = m_position;
  107. m_aabb.bb = vec3((vec2)m_window_size, 0);
  108. //Input::TrackMouse(this);
  109. #if LOL_FEATURE_THREADS
  110. /* Spawn worker threads and wait for their readiness. */
  111. for (int i = 0; i < MAX_THREADS; i++)
  112. m_threads[i] = new thread(std::bind(&Fractal::DoWorkHelper, this, std::placeholders::_1));
  113. for (int i = 0; i < MAX_THREADS; i++)
  114. m_spawnqueue.pop();
  115. #endif
  116. }
  117. ~Fractal()
  118. {
  119. #if LOL_FEATURE_THREADS
  120. /* Signal worker threads for completion and wait for
  121. * them to quit. */
  122. for (int i = 0; i < MAX_THREADS; i++)
  123. m_jobqueue.push(-1);
  124. for (int i = 0; i < MAX_THREADS; i++)
  125. m_donequeue.pop();
  126. #endif
  127. //Input::UntrackMouse(this);
  128. #if !defined __native_client__
  129. Ticker::Unref(m_centertext);
  130. Ticker::Unref(m_mousetext);
  131. Ticker::Unref(m_zoomtext);
  132. #endif
  133. }
  134. inline dcmplx TexelToWorldOffset(vec2 texel)
  135. {
  136. double dx = (0.5 + texel.x - m_size.x / 2) * m_texel2world.x;
  137. double dy = (0.5 + m_size.y / 2 - texel.y) * m_texel2world.y;
  138. return m_radius * dcmplx(dx, dy);
  139. }
  140. inline dcmplx ScreenToWorldOffset(vec2 pixel)
  141. {
  142. /* No 0.5 offset here, because we want to be able to position the
  143. * mouse at (0,0) exactly. */
  144. double dx = pixel.x - m_window_size.x / 2;
  145. double dy = m_window_size.y / 2 - pixel.y;
  146. return m_radius * m_window2world * dcmplx(dx, dy);
  147. }
  148. virtual void TickGame(float seconds)
  149. {
  150. WorldEntity::TickGame(seconds);
  151. ivec2 mousepos = ivec2::zero; /* FIXME: input */
  152. int prev_frame = (m_frame + 4) % 4;
  153. m_frame = (m_frame + 1) % 4;
  154. rcmplx worldmouse = m_center
  155. + rcmplx(ScreenToWorldOffset((vec2)mousepos));
  156. uint32_t buttons = 0;
  157. //uint32_t buttons = Input::GetMouseButtons();
  158. #if !defined _XBOX
  159. if (buttons & 0x2)
  160. {
  161. if (!m_drag)
  162. {
  163. m_oldmouse = mousepos;
  164. m_drag = true;
  165. }
  166. m_translate = rcmplx(ScreenToWorldOffset((vec2)m_oldmouse)
  167. - ScreenToWorldOffset((vec2)mousepos));
  168. /* XXX: the purpose of this hack is to avoid translating by
  169. * an exact number of pixels. If this were to happen, the step()
  170. * optimisation for i915 cards in our shader would behave
  171. * incorrectly because a quarter of the pixels in the image
  172. * would have tied rankings in the distance calculation. */
  173. m_translate *= real(1023.0 / 1024.0);
  174. m_oldmouse = mousepos;
  175. }
  176. else
  177. {
  178. m_drag = false;
  179. if (m_translate != rcmplx(0.0, 0.0))
  180. {
  181. m_translate *= real(std::pow(2.0, -seconds * 5.0));
  182. if ((double)norm(m_translate) < m_radius * 1e-4)
  183. m_translate = rcmplx(0.0, 0.0);
  184. }
  185. }
  186. if (buttons & 0x5 && mousepos.x != -1)
  187. {
  188. double zoom = (buttons & 0x1) ? -0.5 : 0.5;
  189. m_zoom_speed += zoom * seconds;
  190. if (m_zoom_speed / zoom > 5e-3f)
  191. m_zoom_speed = zoom * 5e-3f;
  192. }
  193. else if (m_zoom_speed)
  194. {
  195. m_zoom_speed *= std::pow(2.0, -seconds * 5.0);
  196. if (lol::abs(m_zoom_speed) < 1e-5 || m_drag)
  197. m_zoom_speed = 0.0;
  198. }
  199. #endif
  200. if (m_zoom_speed || m_translate != rcmplx(0.0, 0.0))
  201. {
  202. rcmplx oldcenter = m_center;
  203. double oldradius = m_radius;
  204. double zoom = std::pow(2.0, seconds * 1e3f * m_zoom_speed);
  205. if (m_radius * zoom > 8.0)
  206. {
  207. m_zoom_speed *= -1.0;
  208. zoom = 8.0 / m_radius;
  209. }
  210. else if (m_radius * zoom < 1e-14)
  211. {
  212. m_zoom_speed *= -1.0;
  213. zoom = 1e-14 / m_radius;
  214. }
  215. m_radius *= zoom;
  216. #if !defined _XBOX
  217. m_center += m_translate;
  218. m_center = (m_center - worldmouse) * real(zoom) + worldmouse;
  219. worldmouse = m_center
  220. + rcmplx(ScreenToWorldOffset((vec2)mousepos));
  221. #endif
  222. /* Store the transformation properties to go from m_frame - 1
  223. * to m_frame. */
  224. m_deltashift[prev_frame] = (m_center - oldcenter) / real(oldradius);
  225. m_deltashift[prev_frame].x /= m_size.x * m_texel2world.x;
  226. m_deltashift[prev_frame].y /= m_size.y * m_texel2world.y;
  227. m_deltascale[prev_frame] = m_radius / oldradius;
  228. m_dirty[0] = m_dirty[1] = m_dirty[2] = m_dirty[3] = 2;
  229. }
  230. else
  231. {
  232. /* If settings didn't change, set transformation from previous
  233. * frame to identity. */
  234. m_deltashift[prev_frame] = real::R_0();
  235. m_deltascale[prev_frame] = real::R_1();
  236. }
  237. /* Transformation from current frame to current frame is always
  238. * identity. */
  239. m_zoom_settings[m_frame][0] = 0.0f;
  240. m_zoom_settings[m_frame][1] = 0.0f;
  241. m_zoom_settings[m_frame][2] = 1.0f;
  242. /* Compute transformation from other frames to current frame */
  243. for (int i = 0; i < 3; i++)
  244. {
  245. int prev_index = (m_frame + 4 - i) % 4;
  246. int cur_index = (m_frame + 3 - i) % 4;
  247. m_zoom_settings[cur_index][0] = (real)m_zoom_settings[prev_index][0] * m_deltascale[cur_index] + m_deltashift[cur_index].x;
  248. m_zoom_settings[cur_index][1] = (real)m_zoom_settings[prev_index][1] * m_deltascale[cur_index] + m_deltashift[cur_index].y;
  249. m_zoom_settings[cur_index][2] = (real)m_zoom_settings[prev_index][2] * m_deltascale[cur_index];
  250. }
  251. /* Precompute texture offset change instead of doing it in GLSL */
  252. for (int i = 0; i < 4; i++)
  253. {
  254. m_zoom_settings[i][0] += 0.5f * (1.0f - m_zoom_settings[i][2]);
  255. m_zoom_settings[i][1] -= 0.5f * (1.0f - m_zoom_settings[i][2]);
  256. }
  257. #if !defined __native_client__
  258. char buf[256];
  259. std::sprintf(buf, "center: ");
  260. m_center.x.sprintf(buf + strlen(buf), 30);
  261. std::sprintf(buf + strlen(buf), " ");
  262. m_center.y.sprintf(buf + strlen(buf), 30);
  263. m_centertext->SetText(buf);
  264. std::sprintf(buf, " mouse: ");
  265. worldmouse.x.sprintf(buf + strlen(buf), 30);
  266. std::sprintf(buf + strlen(buf), " ");
  267. worldmouse.y.sprintf(buf + strlen(buf), 30);
  268. m_mousetext->SetText(buf);
  269. std::sprintf(buf, " zoom: %g", 1.0 / m_radius);
  270. m_zoomtext->SetText(buf);
  271. #endif
  272. if (m_dirty[m_frame])
  273. {
  274. m_dirty[m_frame]--;
  275. for (int i = 0; i < m_size.y; i += MAX_LINES * 2)
  276. {
  277. #if LOL_FEATURE_THREADS
  278. m_jobqueue.push(i);
  279. #else
  280. DoWork(i);
  281. #endif
  282. }
  283. }
  284. }
  285. #if LOL_FEATURE_THREADS
  286. void DoWorkHelper(thread *inst)
  287. {
  288. m_spawnqueue.push(0);
  289. for ( ; ; )
  290. {
  291. int line = m_jobqueue.pop();
  292. if (line == -1)
  293. break;
  294. DoWork(line);
  295. m_donequeue.push(0);
  296. }
  297. m_donequeue.push(0);
  298. };
  299. #endif
  300. void DoWork(int line)
  301. {
  302. double const maxsqlen = 1024;
  303. double const k1 = 1.0 / (1 << 10) / (std::log(maxsqlen) / std::log(2.0));
  304. int jmin = ((m_frame + 1) % 4) / 2 + line;
  305. int jmax = jmin + MAX_LINES * 2;
  306. if (jmax > m_size.y)
  307. jmax = m_size.y;
  308. u8vec4 *m_pixelstart = &m_pixels[0]
  309. + m_size.x * (m_size.y / 4 * m_frame + line / 4);
  310. dcmplx c = (dcmplx)m_center;
  311. for (int j = jmin; j < jmax; j += 2)
  312. for (int i = m_frame % 2; i < m_size.x; i += 2)
  313. {
  314. double xr, yr, x0, y0, x1, y1, x2, y2, x3, y3;
  315. dcmplx z0 = c + TexelToWorldOffset(vec2(ivec2(i, j)));
  316. //dcmplx r0(0.28693186889504513, 0.014286693904085048);
  317. //dcmplx r0(0.001643721971153, 0.822467633298876);
  318. //dcmplx r0(-1.207205434596, 0.315432814901);
  319. //dcmplx r0(-0.79192956889854, -0.14632423080102);
  320. //dcmplx r0(0.3245046418497685, 0.04855101129280834);
  321. dcmplx r0 = z0;
  322. x0 = z0.x; y0 = z0.y;
  323. xr = r0.x; yr = r0.y;
  324. int iter = MAX_ITERATIONS - 4;
  325. for (;;)
  326. {
  327. /* Unroll the loop: tests are more expensive to do at each
  328. * iteration than the few extra multiplications. */
  329. x1 = x0 * x0 - y0 * y0 + xr;
  330. y1 = x0 * y0 + x0 * y0 + yr;
  331. x2 = x1 * x1 - y1 * y1 + xr;
  332. y2 = x1 * y1 + x1 * y1 + yr;
  333. x3 = x2 * x2 - y2 * y2 + xr;
  334. y3 = x2 * y2 + x2 * y2 + yr;
  335. x0 = x3 * x3 - y3 * y3 + xr;
  336. y0 = x3 * y3 + x3 * y3 + yr;
  337. if (x0 * x0 + y0 * y0 >= maxsqlen)
  338. break;
  339. iter -= 4;
  340. if (iter < 4)
  341. break;
  342. }
  343. if (iter)
  344. {
  345. double n = x0 * x0 + y0 * y0;
  346. if (x1 * x1 + y1 * y1 >= maxsqlen)
  347. {
  348. iter += 3; n = x1 * x1 + y1 * y1;
  349. }
  350. else if (x2 * x2 + y2 * y2 >= maxsqlen)
  351. {
  352. iter += 2; n = x2 * x2 + y2 * y2;
  353. }
  354. else if (x3 * x3 + y3 * y3 >= maxsqlen)
  355. {
  356. iter += 1; n = x3 * x3 + y3 * y3;
  357. }
  358. if (n > maxsqlen * maxsqlen)
  359. n = maxsqlen * maxsqlen;
  360. /* Approximate log(sqrt(n))/log(sqrt(maxsqlen)) */
  361. double f = iter;
  362. union { double n; uint64_t x; } u = { n };
  363. double k = (double)(u.x >> 42) - (((1 << 10) - 1) << 10);
  364. k *= k1;
  365. /* Approximate log2(k) in [1,2]. */
  366. f += (- 0.344847817623168308695977510213252644185 * k
  367. + 2.024664188044341212602376988171727038739) * k
  368. - 1.674876738008591047163498125918330313237;
  369. *m_pixelstart++ = m_palette[(int)(f * PALETTE_STEP)];
  370. }
  371. else
  372. {
  373. #if defined _XBOX
  374. *m_pixelstart++ = u8vec4(255, 0, 0, 0);
  375. #else
  376. *m_pixelstart++ = u8vec4(0, 0, 0, 255);
  377. #endif
  378. }
  379. }
  380. }
  381. virtual void TickDraw(float seconds, Scene &scene)
  382. {
  383. WorldEntity::TickDraw(seconds, scene);
  384. static float const vertices[] =
  385. {
  386. 1.0f, 1.0f,
  387. -1.0f, 1.0f,
  388. -1.0f, -1.0f,
  389. -1.0f, -1.0f,
  390. 1.0f, -1.0f,
  391. 1.0f, 1.0f,
  392. };
  393. static float const texcoords[] =
  394. {
  395. 1.0f, 1.0f,
  396. 0.0f, 1.0f,
  397. 0.0f, 0.0f,
  398. 0.0f, 0.0f,
  399. 1.0f, 0.0f,
  400. 1.0f, 1.0f,
  401. };
  402. if (!m_ready)
  403. {
  404. /* Create a texture of half the width and twice the height
  405. * so that we can upload four different subimages each frame. */
  406. m_texture = new Texture(ivec2(m_size.x / 2, m_size.y * 2),
  407. PixelFormat::RGBA_8);
  408. /* Ensure the texture data is complete at least once, otherwise
  409. * uploading subimages will not work. */
  410. m_texture->SetData(&m_pixels[0]);
  411. m_shader = Shader::Create(LOLFX_RESOURCE_NAME(11_fractal));
  412. m_vertexattrib = m_shader->GetAttribLocation(VertexUsage::Position, 0);
  413. m_texattrib = m_shader->GetAttribLocation(VertexUsage::TexCoord, 0);
  414. m_texeluni = m_shader->GetUniformLocation("u_TexelSize");
  415. m_screenuni = m_shader->GetUniformLocation("u_ScreenSize");
  416. m_zoomuni = m_shader->GetUniformLocation("u_ZoomSettings");
  417. m_vdecl =
  418. new VertexDeclaration(VertexStream<vec2>(VertexUsage::Position),
  419. VertexStream<vec2>(VertexUsage::TexCoord));
  420. m_vbo = new VertexBuffer(sizeof(vertices));
  421. m_tbo = new VertexBuffer(sizeof(texcoords));
  422. void *tmp = m_vbo->Lock(0, 0);
  423. memcpy(tmp, vertices, sizeof(vertices));
  424. m_vbo->Unlock();
  425. tmp = m_tbo->Lock(0, 0);
  426. memcpy(tmp, texcoords, sizeof(texcoords));
  427. m_tbo->Unlock();
  428. /* FIXME: this object never cleans up */
  429. m_ready = true;
  430. }
  431. m_texture->Bind();
  432. if (m_dirty[m_frame])
  433. {
  434. #if LOL_FEATURE_THREADS
  435. for (int i = 0; i < m_size.y; i += MAX_LINES * 2)
  436. m_donequeue.pop();
  437. #endif
  438. m_dirty[m_frame]--;
  439. m_texture->SetSubData(ivec2(0, m_frame * m_size.y / 2),
  440. m_size / 2,
  441. &m_pixels[m_size.x * m_size.y / 4 * m_frame]);
  442. }
  443. m_shader->Bind();
  444. m_shader->SetUniform(m_texeluni, m_texel_settings);
  445. m_shader->SetUniform(m_screenuni, m_screen_settings);
  446. m_shader->SetUniform(m_zoomuni, m_zoom_settings);
  447. m_vdecl->Bind();
  448. m_vdecl->SetStream(m_vbo, m_vertexattrib);
  449. m_vdecl->SetStream(m_tbo, m_texattrib);
  450. m_texture->Bind();
  451. m_vdecl->DrawElements(MeshPrimitive::Triangles, 0, 6);
  452. m_vdecl->Unbind();
  453. }
  454. private:
  455. static int const MAX_ITERATIONS = 340;
  456. static int const PALETTE_STEP = 32;
  457. static int const MAX_THREADS = 8;
  458. static int const MAX_LINES = 8;
  459. ivec2 m_size, m_window_size, m_oldmouse;
  460. double m_window2world;
  461. dvec2 m_texel2world;
  462. array<u8vec4> m_pixels, m_palette;
  463. Shader *m_shader;
  464. ShaderAttrib m_vertexattrib, m_texattrib;
  465. ShaderUniform m_texeluni, m_screenuni, m_zoomuni;
  466. VertexDeclaration *m_vdecl;
  467. VertexBuffer *m_vbo, *m_tbo;
  468. Texture *m_texture;
  469. int m_frame, m_slices, m_dirty[4];
  470. bool m_ready, m_drag;
  471. rcmplx m_deltashift[4], m_center, m_translate;
  472. real m_deltascale[4];
  473. double m_zoom_speed, m_radius;
  474. vec4 m_texel_settings, m_screen_settings;
  475. mat4 m_zoom_settings;
  476. #if LOL_FEATURE_THREADS
  477. /* Worker threads */
  478. thread *m_threads[MAX_THREADS];
  479. queue<int> m_spawnqueue, m_jobqueue, m_donequeue;
  480. #endif
  481. #if !defined __native_client__
  482. /* Debug information */
  483. Text *m_centertext, *m_mousetext, *m_zoomtext;
  484. #endif
  485. };
  486. int main(int argc, char **argv)
  487. {
  488. ivec2 window_size(640, 480);
  489. System::Init(argc, argv);
  490. Application app("Tutorial 3: Fractal", window_size, 60.0f);
  491. new DebugFps(5, 5);
  492. new Fractal(window_size);
  493. //new DebugRecord("fractalol.ogm", 60.0f);
  494. app.Run();
  495. return EXIT_SUCCESS;
  496. }