X-87 - DOS 256 bytes

本文为原文前 6,000 字符的节选翻译,完整内容请查看原文。

X-87 - DOS 256 bytes link 256 bytes x86 + x87 (386+) / DOS real mode procedural graphics for Outline 2026, VESA 2 (1994) 640x400 32 bpp showing a stylized landscape drawn with oscillators. No escape support.

X-87 - DOS 256 字节链接,这是一个为 Outline 2026 准备的 256 字节 x86 + x87 (386+) / DOS 实模式程序化图形作品。它使用 VESA 2 (1994) 标准,以 640x400 32 bpp 的分辨率展示了一幅由振荡器绘制的风格化风景。不支持退出功能。

This started in early 2024 from p5js sketches i initially made in mid 2021, first sketch was a different idea showing some flow like patterns that i directed towards a ground / sky division later on, i kind of stopped tinkering with it for a year but took it back in early 2025 and directed it into this barren landscape which was mostly found by accident through iterative tweaking of oscillator parameters + transforms. (particularly scale) I liked the moody feel and the accidental “moon”.

该项目始于 2024 年初,源自我 2021 年中期制作的 p5js 草图。最初的草图是一个不同的想法,展示了一些类似流动的图案,后来我将其引导向地面/天空的划分。我曾搁置了一年,但在 2025 年初重新拾起,并将其导向这种荒凉的景观,这主要是通过对振荡器参数和变换(特别是缩放)的迭代调整偶然发现的。我喜欢这种忧郁的感觉和那个意外出现的“月亮”。

One year later i decided to try to fit this into 256 bytes, the code was pretty small but i was unsure about some parts (32 bpp, colors) especially after a semi failed x87 learning experiment. (was still successful as i learned bits of x87 but also a lot of things about high colors / high resolution DOS graphics, all documented here) Took ~13 minutes (!) to render with default (3000; 286 era) DOSBox cycles. ~2m30 at 20000 cycles (late 486 era) and 2m10 at max cycles. (running on a i7 7700 CPU) Took ~6m10 on Bochs + Freedos at 10MIPS. ~8m at 800x600.

一年后,我决定尝试将其压缩到 256 字节以内。代码本身很小,但我对某些部分(32 bpp,颜色)不太确定,特别是在一次半失败的 x87 学习实验之后。(尽管如此,这次实验还是成功的,因为我学到了一些 x87 的知识,也了解了很多关于高色彩/高分辨率 DOS 图形的知识,所有这些都记录在这里。)在默认的 DOSBox 周期(3000;286 时代)下渲染大约需要 13 分钟(!)。在 20000 周期(486 后期)下约为 2 分 30 秒,在最大周期下为 2 分 10 秒(运行在 i7 7700 CPU 上)。在 Bochs + Freedos 以 10MIPS 运行时大约需要 6 分 10 秒。在 800x600 分辨率下约为 8 分钟。

The 256 bytes effort in two weeks First step was porting the p5.js code to something akin to x87 (stack based) and as i fiddled with Forth… it felt like the perfect opportunity to tinker a bit more with Gforth, resulting code was pretty small which further convinced me that shrinking it down to 256 bytes was possible. Second step was a crude (greyscale) DOS port (~320 bytes), learned a bit of x87 in the process, it was kinda fun to see how Forth map so well to x87 but also how it differ on some stuff (x87 is way more flexible sometimes), Forth made the x87 port straightforward and fun.

两周内的 256 字节挑战:第一步是将 p5.js 代码移植到类似于 x87(基于堆栈)的形式。当我摆弄 Forth 时……我觉得这是一个进一步尝试 Gforth 的绝佳机会,生成的代码非常小,这进一步让我相信将其压缩到 256 字节是可能的。第二步是一个粗糙的(灰度)DOS 移植版(约 320 字节),在此过程中学到了一些 x87 知识。看到 Forth 与 x87 的映射如此完美,以及它们在某些方面的差异(x87 有时更灵活),这很有趣。Forth 让 x87 的移植变得简单且有趣。

Third step was an early optimization phase (FPU code, constants) to free rooms for the colors, the landscape was kind of dull at that moment. Final step was to integrate the dither like colorization (colors increment depends on averaged pixels brightness) then try to make it less dull by tweaking constants, this last part added some more weight which required further optimizations : overlapped / truncated floating points (got the idea from balrog), packed colors increments, less FPU instructions etc.

第三步是早期的优化阶段(FPU 代码、常量),为颜色腾出空间,当时的景观显得有些单调。最后一步是集成类似抖动的着色(颜色增量取决于平均像素亮度),然后通过调整常量尝试使其不那么单调。最后这一部分增加了一些负担,需要进一步优化:重叠/截断浮点数(灵感来自 balrog)、打包颜色增量、减少 FPU 指令等。

The last bytes + compat Program was considered finished at this point but there was many compat issues left (no fninit, uninitialized mem etc.) and tiny black squares artifacts (almost imperceptible on DOSBox but very large on Bochs !) which was related to the tricky segment initialization and VESA paging / banking, last goal was to fix them. It was rather simple to get few bytes to fix the artifacts by reusing palette data for a float constant (overlapping) and further optimizing the init code and px_sat loop code by reusing bx state for the loop.

最后的字节与兼容性:程序此时被认为已完成,但仍存在许多兼容性问题(没有 fninit,未初始化的内存等)以及微小的黑色方块伪影(在 DOSBox 上几乎察觉不到,但在 Bochs 上非常明显!),这与棘手的段初始化和 VESA 分页/分块有关,最后一个目标是修复它们。通过重用调色板数据作为浮点常量(重叠)并进一步优化初始化代码和 px_sat 循环代码(通过重用循环的 bx 状态),修复这些伪影所需的字节数非常少。

Making a compatible version (one that run on Bochs + Freedos at least, didn’t test on real HW) at 256 bytes was slightly harder and took me a few days, biggest win was to pack the two main loops into a single one and using the upper / lower half of ECX as a frame counter / osc. index, FPU code was again optimized. (trying hard to avoid fcomi instruction to stay at 386 era)

制作一个 256 字节的兼容版本(至少能在 Bochs + Freedos 上运行,未在真实硬件上测试)稍微困难一些,花了我几天时间。最大的收获是将两个主循环打包成一个,并使用 ECX 的高/低位作为帧计数器/振荡器索引,FPU 代码再次得到了优化。(努力避免使用 fcomi 指令以保持在 386 时代)。

Conclusion Satisfied of the final result overall as it is quite close to the prototype, not entirely convinced that 32 bpp is justified… could perhaps be done in 256 colors. (in a smaller way ?) The image can be scaled to any resolutions (require a constant + bank check constant change) but convergence happen faster at moderate resolution + artifacts are less visible. (see below; has artifacts when vertical axis is extended) Resolution also affect brightness / density although this can be fixed with colors increment / oscillators adjustments.

结论:总体上对最终结果感到满意,因为它与原型非常接近。并不完全确信 32 bpp 是合理的……也许可以用 256 色完成。(以更小的方式?)图像可以缩放到任何分辨率(需要更改常量 + 银行检查常量),但在中等分辨率下收敛更快,伪影也不那么明显。(见下文;垂直轴扩展时会出现伪影)。分辨率也会影响亮度和密度,尽管这可以通过颜色增量/振荡器调整来修复。