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.

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