{"id":5785,"date":"2026-02-01T10:00:24","date_gmt":"2026-02-01T17:00:24","guid":{"rendered":"https:\/\/rec0m88.com\/?p=5785"},"modified":"2026-02-02T10:58:00","modified_gmt":"2026-02-02T17:58:00","slug":"optimizing-the-game-loop-the-battle-for-frame-pacing-in-the-browser","status":"publish","type":"post","link":"https:\/\/rec0m88.com\/cs\/optimizing-the-game-loop-the-battle-for-frame-pacing-in-the-browser\/","title":{"rendered":"Optimalizace hern\u00ed smy\u010dky: Bitva o tempo sn\u00edmk\u016f v prohl\u00ed\u017ee\u010di"},"content":{"rendered":"<div class=\"wp-block-rank-math-toc-block\" id=\"rank-math-toc\"><h2>Obsah<\/h2><nav><ul><li><a href=\"#the-v-sync-ghost-why-60-hz-isnt-always-60-hz\">Duch V-Sync: Pro\u010d 60 Hz nen\u00ed v\u017edy 60 Hz<\/a><ul><li><a href=\"#our-solution-request-animation-frame-and-the-web-audio-clock\">Na\u0161e \u0159e\u0161en\u00ed: requestAnimationFrame a webov\u00e9 zvukov\u00e9 hodiny<\/a><\/li><\/ul><\/li><li><a href=\"#the-audio-crackle-solving-the-buffer-underflow\">Zvukov\u00fd praskot: \u0158e\u0161en\u00ed probl\u00e9mu \u201cBuffer Underflow\u201d<\/a><\/li><li><a href=\"#managing-jitter-in-a-jit-world\">Spr\u00e1va zpo\u017ed\u011bn\u00ed ve sv\u011bt\u011b JIT<\/a><\/li><li><a href=\"#the-input-to-photon-latency\">Zpo\u017ed\u011bn\u00ed mezi vstupem a fotonem<\/a><\/li><li><a href=\"#why-high-value-sites-care-about-timing\">Pro\u010d se weby s vysokou hodnotou staraj\u00ed o na\u010dasov\u00e1n\u00ed<\/a><\/li><\/ul><\/nav><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Pro n\u00e1hodn\u00e9ho pozorovatele je hra jen s\u00e9ri\u00ed pohybliv\u00fdch obr\u00e1zk\u016f. Ale pro v\u00fdvoj\u00e1\u0159e je hra ne\u00faprosn\u00fd, rychl\u00fd hodinov\u00fd stroj. Pro retro emul\u00e1tory a ark\u00e1dov\u00e9 tituly v HTML5 jsou tyto hodiny ne\u00faprosn\u00e9. Pokud se \u010dasov\u00e1n\u00ed posune by\u0165 jen o n\u011bkolik milisekund, zvuk bude praskat, posouv\u00e1n\u00ed se bude \u201czasek\u00e1vat\u201d a hra nebude reagovat.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Na adrese <strong>Rec0m88<\/strong>, nejv\u011bt\u0161\u00ed technickou p\u0159ek\u00e1\u017ekou nen\u00ed jen spu\u0161t\u011bn\u00ed hry, ale i zvl\u00e1dnut\u00ed jej\u00edho ovl\u00e1d\u00e1n\u00ed. <strong>Rozlo\u017een\u00ed sn\u00edmk\u016f<\/strong>. JavaScript byl p\u016fvodn\u011b vytvo\u0159en pro posouv\u00e1n\u00ed textu a klik\u00e1n\u00ed na tla\u010d\u00edtka, nikoliv pro zvl\u00e1dnut\u00ed p\u0159esnosti, kter\u00e1 se pohybuje pod milisekundou, jakou vy\u017eaduj\u00ed ark\u00e1dov\u00e9 desky z 90. let. Zde se dozv\u00edte, jak jsme zkrotili strojovnu prohl\u00ed\u017ee\u010de, abychom dos\u00e1hli plynul\u00e9ho a konzistentn\u00edho hran\u00ed p\u0159i 60 FPS.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"the-v-sync-ghost-why-60-hz-isnt-always-60-hz\">Duch V-Sync: Pro\u010d 60 Hz nen\u00ed v\u017edy 60 Hz<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">V\u011bt\u0161ina retro konzol\u00ed byla navr\u017eena pro televizory NTSC, kter\u00e9 b\u011b\u017e\u00ed p\u0159esn\u011b na frekvenci. <strong>59,94 Hz<\/strong>. Modern\u00ed po\u010d\u00edta\u010dov\u00e9 monitory v\u0161ak obvykle b\u011b\u017e\u00ed na rovn\u00e9 frekvenci. <strong>60 Hz<\/strong>, <strong>120 Hz<\/strong>, nebo <strong>144 Hz<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Pokud spust\u00edte hru s frekvenc\u00ed 59,94 Hz na 60Hz monitoru, naraz\u00edte na matematickou no\u010dn\u00ed m\u016fru. Ka\u017ed\u00fdch n\u011bkolik sekund monitor \u201cvynech\u00e1\u201d sn\u00edmek, proto\u017ee \u010dasov\u00e1n\u00ed nen\u00ed v dokonal\u00e9m souladu. To m\u00e1 za n\u00e1sledek <strong>Mikrokokt\u00e1n\u00ed<\/strong>-ten otravn\u00fd \u201cskok\u201d, kter\u00fd vid\u00edte, kdy\u017e postava p\u0159ech\u00e1z\u00ed po obrazovce.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"our-solution-request-animation-frame-and-the-web-audio-clock\">Na\u0161e \u0159e\u0161en\u00ed: <code>requestAnimationFrame<\/code> a webov\u00e9 zvukov\u00e9 hodiny<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Ve standardn\u00ed webov\u00e9 aplikaci pou\u017e\u00edvaj\u00ed v\u00fdvoj\u00e1\u0159i <code>setTimeout<\/code> nebo <code>setInterval<\/code> ke spu\u0161t\u011bn\u00ed k\u00f3du. Ty jsou pro hran\u00ed her p\u0159\u00edli\u0161 \u201cvoln\u00e9\u201d; prohl\u00ed\u017ee\u010d je m\u016f\u017ee zdr\u017eovat, kdykoli se mu zd\u00e1, \u017ee je zanepr\u00e1zdn\u011bn.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">M\u00edsto toho pou\u017e\u00edv\u00e1 Rec0m88 syst\u00e9m dvou takt\u016f:<\/p>\n\n\n\n<ol start=\"1\" class=\"wp-block-list\">\n<li><strong>Vizu\u00e1ln\u00ed synchronizace:<\/strong> Pou\u017e\u00edv\u00e1me <code>requestAnimationFrame<\/code> (rAF). To prohl\u00ed\u017ee\u010di \u0159\u00edk\u00e1: \u201cDal\u0161\u00ed sn\u00edmek hry spus\u0165te, a\u017e kdy\u017e je monitor p\u0159ipraven jej vykreslit.\u201d T\u00edm se eliminuje \u201ctrh\u00e1n\u00ed obrazovky\u201d a video je plynul\u00e9 jako hedv\u00e1b\u00ed.<\/li>\n\n\n\n<li><strong>Synchronizace zvuku:<\/strong> Vzhledem k tomu, \u017ee obnovovac\u00ed frekvence monitoru m\u016f\u017ee kol\u00edsat, pou\u017e\u00edv\u00e1me hodnotu <strong>Kontextov\u00fd \u010das rozhran\u00ed Web Audio API<\/strong> jako n\u00e1\u0161 hlavn\u00ed \u201cmetronom\u201d. Hodiny Web Audio jsou nejp\u0159esn\u011bj\u0161\u00ed \u010dasova\u010d v prohl\u00ed\u017ee\u010di. Uzam\u010den\u00edm na\u0161\u00ed hern\u00ed smy\u010dky WebAssembly (WASM) k vyrovn\u00e1vac\u00ed pam\u011bti zvuku zajist\u00edme, \u017ee zvuk nikdy nedojde k desynchronizaci s akc\u00ed.<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"the-audio-crackle-solving-the-buffer-underflow\">Zvukov\u00fd praskot: \u0158e\u0161en\u00ed probl\u00e9mu \u201cBuffer Underflow\u201d<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Hr\u00e1li jste n\u011bkdy webovou hru, p\u0159i kter\u00e9 p\u0159i otev\u0159en\u00ed nov\u00e9 karty zvuk prask\u00e1 nebo prask\u00e1? To je <strong>Podte\u010den\u00ed vyrovn\u00e1vac\u00ed pam\u011bti<\/strong>. St\u00e1v\u00e1 se to proto, \u017ee hlavn\u00ed vl\u00e1kno prohl\u00ed\u017ee\u010de bylo p\u0159\u00edli\u0161 zanepr\u00e1zdn\u011bno, aby \u201cnakrmilo\u201d zvukovou kartu dal\u0161\u00edmi n\u011bkolika milisekundami zvuku.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Abychom to v syst\u00e9mu Rec0m88 vy\u0159e\u0161ili, p\u0159esunuli jsme zpracov\u00e1n\u00ed zvuku do rozhran\u00ed <strong>Webov\u00fd pracovn\u00edk<\/strong>.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Odd\u011blen\u00e1 logika:<\/strong> Hern\u00ed logika b\u011b\u017e\u00ed v jednom vl\u00e1kn\u011b a mixov\u00e1n\u00ed zvuku v jin\u00e9m.<\/li>\n\n\n\n<li><strong>Kruhov\u00e9 n\u00e1razn\u00edky:<\/strong> Udr\u017eujeme malou vyrovn\u00e1vac\u00ed pam\u011b\u0165 \u201clook-ahead\u201d se zvukov\u00fdmi vzorky. Pokud dojde k milisekundov\u00e9mu n\u00e1r\u016fstu v\u00fdkonu procesoru, p\u0159ehr\u00e1va\u010d si z t\u00e9to vyrovn\u00e1vac\u00ed pam\u011bti vezme zvukov\u00fd vzorek, tak\u017ee syst\u00e9m m\u00e1 \u010das se vzpamatovat, ani\u017e by u\u017eivatel sly\u0161el \u201cprasknut\u00ed\u201d.\u201d<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"managing-jitter-in-a-jit-world\">Spr\u00e1va zpo\u017ed\u011bn\u00ed ve sv\u011bt\u011b JIT<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">JavaScript je <strong>JIT (Just-In-Time)<\/strong> zkompilovan\u00fd jazyk. To znamen\u00e1, \u017ee prohl\u00ed\u017ee\u010d b\u011bhem hran\u00ed neust\u00e1le \u201coptimalizuje\u201d k\u00f3d. N\u011bkdy tyto optimalizace zp\u016fsob\u00ed chvilkov\u00e9 \u201czaseknut\u00ed\u201d (\u010dasto naz\u00fdvan\u00e9 \u201cJank\u201d).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Abychom dos\u00e1hli dokonal\u00e9ho tempa sn\u00edmk\u016f, minimalizujeme \u201cGarbage Collection\u201d (GC). GC je situace, kdy prohl\u00ed\u017ee\u010d zastav\u00ed v\u0161e, aby vy\u010distil nepou\u017e\u00edvanou pam\u011b\u0165. Tomu se vyhneme t\u00edm, \u017ee:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Sdru\u017eov\u00e1n\u00ed objekt\u016f:<\/strong> Pro hern\u00ed sprity a pakety P2P opakovan\u011b pou\u017e\u00edv\u00e1me stejn\u00e9 pam\u011b\u0165ov\u00e9 bloky, m\u00edsto abychom vytv\u00e1\u0159eli nov\u00e9.<\/li>\n\n\n\n<li><strong>Typov\u00e1 pole:<\/strong> Pou\u017e\u00edv\u00e1me <code>Uint8Array<\/code> a <code>Float32Array<\/code> pro zpracov\u00e1n\u00ed hern\u00edch dat. Ta jsou ulo\u017eena v pevn\u011b dan\u00e9 oblasti pam\u011bti, kterou prohl\u00ed\u017ee\u010d nemus\u00ed tak \u010dasto \u201c\u010distit\u201d, \u010d\u00edm\u017e se zachov\u00e1v\u00e1 p\u0159edv\u00eddatelnost hern\u00ed smy\u010dky.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"the-input-to-photon-latency\">Zpo\u017ed\u011bn\u00ed mezi vstupem a fotonem<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">\u201cFrame Pacing\u201d nen\u00ed jen o tom, co vid\u00edte, ale tak\u00e9 o tom, co c\u00edt\u00edte. Doba od stisknut\u00ed kl\u00e1vesy do okam\u017eiku, kdy pixel zm\u011bn\u00ed barvu, se naz\u00fdv\u00e1 <strong>Zpo\u017ed\u011bn\u00ed mezi vstupem a fotonem<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Pomoc\u00ed <strong>Webov\u00e9 rozhran\u00ed HID API<\/strong> a <strong>Rozhran\u00ed API pro gamepad<\/strong>, obejdeme standardn\u00ed frontu \u201cUd\u00e1losti kl\u00e1vesnice\u201d prohl\u00ed\u017ee\u010de. To n\u00e1m umo\u017e\u0148uje dotazovat se \u0159adi\u010de na za\u010d\u00e1tku ka\u017ed\u00e9ho sn\u00edmku, t\u011bsn\u011b p\u0159ed spu\u0161t\u011bn\u00edm j\u00e1dra WASM. T\u00edm je zaji\u0161t\u011bno, \u017ee v\u00e1\u0161 vstup bude zpracov\u00e1n v <em>hned dal\u0161\u00ed<\/em> r\u00e1m, kter\u00fd poskytuje \u201csvi\u017en\u00fd\u201d pocit, kter\u00fd ark\u00e1dov\u00ed nad\u0161enci vy\u017eaduj\u00ed.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"why-high-value-sites-care-about-timing\">Pro\u010d se weby s vysokou hodnotou staraj\u00ed o na\u010dasov\u00e1n\u00ed<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Filtr Google \u201cObsah s n\u00edzkou hodnotou\u201d se ned\u00edv\u00e1 pouze na text, ale tak\u00e9 na to. <strong>u\u017eivatelsk\u00e1 zku\u0161enost<\/strong>. Str\u00e1nka, kter\u00e1 poskytuje zpo\u017ed\u011bn\u00fd, zadrh\u00e1vaj\u00edc\u00ed se emul\u00e1tor, je \u201cnekvalitn\u00ed\u201d n\u00e1stroj. T\u00edm, \u017ee dokumentujeme n\u00e1\u0161 boj s tempem sn\u00edmk\u016f a poskytujeme vysoce p\u0159esn\u00fd engine, dokazujeme, \u017ee Rec0m88 je platforma profesion\u00e1ln\u00ed kvality.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Stovky hodin jsme str\u00e1vili lad\u011bn\u00edm interakce mezi rozhran\u00edm Web Audio API, WebAssembly a grafick\u00fdm procesorem, abychom zajistili, \u017ee p\u0159i p\u0159ehr\u00e1v\u00e1n\u00ed klasick\u00fdch her na na\u0161\u00ed platform\u011b to nejen funguje, ale i funguje.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>V\u00fdzva Meta:<\/strong> Zkuste si p\u0159i hran\u00ed n\u011bkter\u00e9 z na\u0161ich her otev\u0159\u00edt \u201cMonitor v\u00fdkonu\u201d v prohl\u00ed\u017ee\u010di. Uvid\u00edte rovnou, konzistentn\u00ed \u010d\u00e1ru 60 FPS - d\u016fkaz na\u0161\u00ed architektury sn\u00edmkov\u00e9ho kmito\u010dtu.<\/p>","protected":false},"excerpt":{"rendered":"<p>To the casual observer, a game is just a series of moving images. But to a developer, a game is a relentless, high-speed clock. For retro emulators and HTML5 arcade titles, that clock is unforgiving. If the timing slips by even a few milliseconds, the audio will crackle, the scrolling will &#8220;hitch,&#8221; and the gameplay&#8230;<\/p>","protected":false},"author":1,"featured_media":5764,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_kad_post_transparent":"","_kad_post_title":"","_kad_post_layout":"","_kad_post_sidebar_id":"","_kad_post_content_style":"","_kad_post_vertical_padding":"","_kad_post_feature":"","_kad_post_feature_position":"","_kad_post_header":false,"_kad_post_footer":false,"_kad_post_classname":"","footnotes":""},"categories":[40],"tags":[],"class_list":["post-5785","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-meta"],"_links":{"self":[{"href":"https:\/\/rec0m88.com\/cs\/wp-json\/wp\/v2\/posts\/5785","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/rec0m88.com\/cs\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/rec0m88.com\/cs\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/rec0m88.com\/cs\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/rec0m88.com\/cs\/wp-json\/wp\/v2\/comments?post=5785"}],"version-history":[{"count":1,"href":"https:\/\/rec0m88.com\/cs\/wp-json\/wp\/v2\/posts\/5785\/revisions"}],"predecessor-version":[{"id":5786,"href":"https:\/\/rec0m88.com\/cs\/wp-json\/wp\/v2\/posts\/5785\/revisions\/5786"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/rec0m88.com\/cs\/wp-json\/wp\/v2\/media\/5764"}],"wp:attachment":[{"href":"https:\/\/rec0m88.com\/cs\/wp-json\/wp\/v2\/media?parent=5785"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/rec0m88.com\/cs\/wp-json\/wp\/v2\/categories?post=5785"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/rec0m88.com\/cs\/wp-json\/wp\/v2\/tags?post=5785"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}