{"id":2749,"date":"2023-04-22T17:41:45","date_gmt":"2023-04-22T17:41:45","guid":{"rendered":"https:\/\/cml-a.com\/content\/?p=2749"},"modified":"2025-03-19T05:20:36","modified_gmt":"2025-03-19T05:20:36","slug":"wormhole","status":"publish","type":"post","link":"https:\/\/cml-a.com\/content\/2023\/04\/22\/wormhole\/","title":{"rendered":"Wormhole"},"content":{"rendered":"\n<p>Do you remember <a href=\"https:\/\/learn.microsoft.com\/en-us\/windows\/win32\/directdraw\/directdraw-reference\">DirectDraw<\/a>? The DirectX 5 SDK disc came with a bunch of samples, including one called \"Wormhole\". <\/p>\n\n\n\n<p>Looks like this:<\/p>\n\n\n\n<figure class=\"wp-block-video\"><video controls src=\"https:\/\/cml-a.com\/content\/wp-content\/uploads\/2023\/04\/wormhole_dx.mp4\"><\/video><\/figure>\n\n\n\n<p>How it works: despite how the image looks animated, there's no change to the framebuffer data. It's all palette rotation. The sample comes with a bitmap specially chosen so that the colors rotate to produce this 'wormhole' animation. <\/p>\n\n\n\n<p>If you want to try it yourself, load it up from a DirectX 5 SDK disc (it's on some other SDK version discs, as well). Or, you can find it on the Internet Archive here: <a href=\"https:\/\/archive.org\/details\/idx5sdk\">https:\/\/archive.org\/details\/idx5sdk<\/a>.<\/p>\n\n\n\n<p>My project: ported this sample to C256 Foenix. <em>(Update: I later also ported it to F256 Foenix.)<\/em><\/p>\n\n\n\n<p>This is a language (C to 65816) and platform (Win32+DirectDraw to C256 Foenix + Vicky II) port.<\/p>\n\n\n\n<p>Some of the challenges were:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Making sure the right bitmap with the right palette gets initialized. See, it's not sufficient to simply read RGB of the original bitmap and emit a new one that looks visually equivalent. The original bitmap's palette needs to be preserved. It contains \"dead\" colors- colors that aren't referenced by any one pixel as you view it, but are important to the rotation effect. I wrote a tool called <a href=\"https:\/\/github.com\/clandrew\/fnxapp\/blob\/main\/BitmapEmbedder\/FlexiblePalette\/BitmapEmbedder.cpp\">BitmapEmbedder <\/a>to take care of this.<\/li>\n\n\n\n<li>Betting on how long, in terms of clock, the rotation effect would take to execute. I was bold and put it all in VBLANK handler. Fortunately it fit and I didn't optimize for perf super aggressively. I had no idea whether it would fit. If it didn't, I would've to pull a bunch of it out and synchronize it. And it would be easier to do that at the beginning, before it's all set up. I took the risk at the beginning that it would fit and this paid off.<\/li>\n\n\n\n<li>Having a loop that needed to be longer than the signed branch distance limit. I could have maybe added a \"hop\" to get back to the beginning of the loop. Instead I factored out a function for no reason other than to get past the limit. It doesn't make me feel great. Could be something to revisit later.<\/li>\n<\/ul>\n\n\n\n<p>A bunch of other things worked well. Vicky II has a dedicated bitmap layer that you can cleanly copy to. I say cleanly because it was a lot easier to work with compared to Apple II, and SNES for that matter. There isn't any weird swizzling, interleaving or holes. It was exactly compatible with a DirectDraw surface in terms of indexed color and surface size.<\/p>\n\n\n\n<p>Result looks like: (comparison between the original and the port)<\/p>\n\n\n\n<figure class=\"wp-block-video\"><video controls src=\"https:\/\/cml-a.com\/content\/wp-content\/uploads\/2023\/04\/DDAndVicky.mp4\"><\/video><\/figure>\n\n\n\n<p>If you aren't familiar with the concept of palette rotation:<\/p>\n\n\n\n<p>Palette rotation is a visual effect made possible by storing image data in a compact way. <\/p>\n\n\n\n<p>You might be familiar with not-very-compact ways to store image data. For each pixel, say, you store a red, green and blue color value. Functionally that works, no worries. But the memory cost- even if each color channel is only two-thirds of a byte, then each pixel will still take up two bytes. Or if each color channel is a byte, you're looking at three bytes then. Or even four if you use alpha. The memory cost can really add up to more than you can afford.<\/p>\n\n\n\n<p>There's a more compact way to store image data. You can store <em>indexed<\/em> <em>color <\/em>instead. For each pixel, store a key. The key is only 1 byte, not 4. It's a number from 0 to 255. When the computer displays the image on the screen, it will use that key to look up into a <em>palette, <\/em>or table of colors. In a way, this limits image quality, since you can only have an image with a low total number of colors (256). But you save a lot of memory. After all, each pixel takes up only one byte.<\/p>\n\n\n\n<p>There are different configurations of key size affecting how many colors you can use at a time. You could sacrifice image quality to optimize for memory even more. Like anything there are tradeoffs. Having a key be one byte is a popular choice though, and this is supported on Vicky II.<\/p>\n\n\n\n<p>Ordinarily, it'd cost a lot of perf to implement palette lookups yourself in your software. \"For each pixel, look up into the palette, assign a color...\" It's be so slow. Fortunately, indexed color is an industry-recognized idea that has built-in hardware acceleration on a ton of platforms, including on Vicky II. That's where the benefit really shines, so you don't have to worry.<\/p>\n\n\n\n<p>Anyway, as you see with indexed color, there's <em>indirection<\/em>. Change one entry in the palette, a simple one-byte change, and it could affect half your image or more. Because of the <em>indirection<\/em> used with indexed color, an effective way to animate things can be to not animate the image data at all, but to simply make a small change to the palette. The palette has way fewer bytes of data, yet the capacity to change how the whole image looks.<\/p>\n\n\n\n<p>Palette rotation can also be called <em>color cycling<\/em>. There are some beautiful artworks using color cycling to convey water, snow, or other effects. For example, see this snow effect from this <a href=\"http:\/\/www.effectgames.com\/demos\/canvascycle\/\">demo page<\/a> (not my page):<\/p>\n\n\n\n<figure class=\"wp-block-video\"><video controls src=\"https:\/\/cml-a.com\/content\/wp-content\/uploads\/2023\/04\/Snow.mp4\"><\/video><\/figure>\n\n\n\n<p>The grid in the lower right shows the palette being changed.<\/p>\n\n\n\n<p>Or this one, with rain:<\/p>\n\n\n\n<figure class=\"wp-block-video\"><video controls src=\"https:\/\/cml-a.com\/content\/wp-content\/uploads\/2023\/04\/Rain.mp4\"><\/video><\/figure>\n\n\n\n<p>The Wormhole sample uses the idea of palette rotation to achieve an animation effect. It only copies the original bitmap data once on application start. It never touches it again. <\/p>\n\n\n\n<p>Every VBLANK handler, it only updates the palette. And although it does a lot of manipulations to the palette-- there's four loops, iterating over various parts of it, copying entries around-- it can still be way less expensive than an alternative way of animating things- iterating over every pixel in the bitmap. This way, you can exploit this compactness in the image format to get a performance benefit too.<\/p>\n\n\n\n<p>Source code available here:<\/p>\n\n\n\n<p><a href=\"https:\/\/github.com\/clandrew\/wormhole\/blob\/main\/vickyii\/wormhole.s\">https:\/\/github.com\/clandrew\/wormhole\/blob\/main\/vickyii\/wormhole.s<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Do you remember DirectDraw? The DirectX 5 SDK disc came with a bunch of samples, including one called &#8220;Wormhole&#8221;. Looks like this: How it works: despite how the image looks animated, there&#8217;s no change to the framebuffer data. It&#8217;s all palette rotation. The sample comes with a bitmap specially chosen so that the colors rotate [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[4,65,150,178],"class_list":["post-2749","post","type-post","status-publish","format-standard","hentry","category-uncategorized","tag-4","tag-foenix","tag-retro","tag-software-development-project"],"_links":{"self":[{"href":"https:\/\/cml-a.com\/content\/wp-json\/wp\/v2\/posts\/2749","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cml-a.com\/content\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cml-a.com\/content\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cml-a.com\/content\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/cml-a.com\/content\/wp-json\/wp\/v2\/comments?post=2749"}],"version-history":[{"count":2,"href":"https:\/\/cml-a.com\/content\/wp-json\/wp\/v2\/posts\/2749\/revisions"}],"predecessor-version":[{"id":2932,"href":"https:\/\/cml-a.com\/content\/wp-json\/wp\/v2\/posts\/2749\/revisions\/2932"}],"wp:attachment":[{"href":"https:\/\/cml-a.com\/content\/wp-json\/wp\/v2\/media?parent=2749"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cml-a.com\/content\/wp-json\/wp\/v2\/categories?post=2749"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cml-a.com\/content\/wp-json\/wp\/v2\/tags?post=2749"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}