<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
		<id>http://www.sizecoding.org/index.php?feed=atom&amp;namespace=0&amp;title=Special%3ANewPages</id>
		<title>SizeCoding - New pages [en]</title>
		<link rel="self" type="application/atom+xml" href="http://www.sizecoding.org/index.php?feed=atom&amp;namespace=0&amp;title=Special%3ANewPages"/>
		<link rel="alternate" type="text/html" href="http://www.sizecoding.org/wiki/Special:NewPages"/>
		<updated>2026-04-17T15:03:32Z</updated>
		<subtitle>From SizeCoding</subtitle>
		<generator>MediaWiki 1.27.0</generator>

	<entry>
		<id>http://www.sizecoding.org/wiki/Sega_MegaDrive</id>
		<title>Sega MegaDrive</title>
		<link rel="alternate" type="text/html" href="http://www.sizecoding.org/wiki/Sega_MegaDrive"/>
				<updated>2025-12-18T22:12:49Z</updated>
		
		<summary type="html">&lt;p&gt;Leaq: fix typos&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== SEGA MegaDrive / Genesis ==&lt;br /&gt;
&lt;br /&gt;
The SEGA MegaDrive / Genesis is a 16-bit videogame console released by SEGA in 1989.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
It features:&lt;br /&gt;
* 320x224 screen resolution by default, read &amp;quot;Screen Resolutions&amp;quot; for more information.&lt;br /&gt;
* 4x 16 color palettes.&lt;br /&gt;
* Tile based display chip with 2 scrolling tilemaps.&lt;br /&gt;
* Full plane or 16 pixel column independent vertical scroll.&lt;br /&gt;
* Full plane, 8 pixel row or scanline independent horizontal scroll.&lt;br /&gt;
* 80 sprites per frame in H40 mode, 64 sprites in H32 mode. Read &amp;quot;Screen Resolutions&amp;quot; for more information.&lt;br /&gt;
* 20 sprites per scanline in H40 mode, 16 sprites in H32 mode.&lt;br /&gt;
* 64K of dedicated Video Memory (VRAM)&lt;br /&gt;
* YM2612 FM Sound Chip + SN76489 PSG Sound Chip (Integrated in the VDP)&lt;br /&gt;
&lt;br /&gt;
=== Setting up ===&lt;br /&gt;
* Assembler: VASM&lt;br /&gt;
* Emulator(s): BlastEm ( https://www.retrodev.com/blastem/ )&lt;br /&gt;
* Tool(s): MDTools ( https://github.com/sikthehedgehog/mdtools )&lt;br /&gt;
* Hardware: Sega Mega Drive / Genesis console with an everdrive.&lt;br /&gt;
&lt;br /&gt;
Cartridges have a ROM header that can normally be ditched entirely, since it's only used by emulators to display information about the cartridge or choose correct emulation settings / peripherals. This is not true for all console revisions, though; one part of the header is mandatory for systems equiped with the TradeMark Security System. Read &amp;quot;ROM Header&amp;quot; and &amp;quot;TMSS&amp;quot; for more information.&lt;br /&gt;
&lt;br /&gt;
=== CPU Vector table ===&lt;br /&gt;
This must be the very first thing in the ROM. On power up or reset the CPU initializes the stack pointer held at address $000000 and jumps to the reset vector held at address $000004. The rest of the vectors can be discarded competely, since they are only used for exception handling and video interrupts (more information about interrupts can be found in the &amp;quot;VDP&amp;quot; section).&lt;br /&gt;
If a cartridge doesn't want to process a particular interrupt, or wants to ignore it, standard procedure is to set that vector to a dummy handler that simply returns from the exception (RTE). For our purposes, if only the stack pointer and reset vector will be used, we can use the remaining address space used by the vector table as executable code space.&lt;br /&gt;
&lt;br /&gt;
Below are examples that illustrate how the vector table is conventionally set up and how we might want to do it.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;&amp;quot;&amp;gt;&lt;br /&gt;
	; Example 1: Standard procedure, where all vectors are initialized to something.&lt;br /&gt;
	; If a particular vector is not to be used, a &amp;quot;null&amp;quot; or &amp;quot;dummy&amp;quot; handler is put.&lt;br /&gt;
	dc.l   0x00FFE000			; Initial stack pointer value&lt;br /&gt;
	dc.l   CPU_EntryPoint		; Start of program&lt;br /&gt;
	dc.l   CPU_Exception		; Bus error&lt;br /&gt;
	dc.l   CPU_Exception		; Address error&lt;br /&gt;
	dc.l   CPU_Exception		; Illegal instruction&lt;br /&gt;
	dc.l   CPU_Exception		; Division by zero&lt;br /&gt;
	dc.l   CPU_Exception		; CHK CPU_Exception&lt;br /&gt;
	dc.l   CPU_Exception		; TRAPV CPU_Exception&lt;br /&gt;
	dc.l   CPU_Exception		; Privilege violation&lt;br /&gt;
	dc.l   INT_Null				; TRACE exception&lt;br /&gt;
	dc.l   INT_Null				; Line-A emulator&lt;br /&gt;
	dc.l   INT_Null				; Line-F emulator&lt;br /&gt;
	dc.l   INT_Null				; Unused (reserved)&lt;br /&gt;
	dc.l   INT_Null				; Unused (reserved)&lt;br /&gt;
	dc.l   INT_Null				; Unused (reserved)&lt;br /&gt;
	dc.l   INT_Null				; Unused (reserved)&lt;br /&gt;
	dc.l   INT_Null				; Unused (reserved)&lt;br /&gt;
	dc.l   INT_Null				; Unused (reserved)&lt;br /&gt;
	dc.l   INT_Null				; Unused (reserved)&lt;br /&gt;
	dc.l   INT_Null				; Unused (reserved)&lt;br /&gt;
	dc.l   INT_Null				; Unused (reserved)&lt;br /&gt;
	dc.l   INT_Null				; Unused (reserved)&lt;br /&gt;
	dc.l   INT_Null				; Unused (reserved)&lt;br /&gt;
	dc.l   INT_Null				; Unused (reserved)&lt;br /&gt;
	dc.l   INT_Null				; Spurious exception&lt;br /&gt;
	dc.l   INT_Null				; IRQ level 1&lt;br /&gt;
	dc.l   INT_Null				; IRQ level 2&lt;br /&gt;
	dc.l   INT_Null				; IRQ level 3&lt;br /&gt;
	dc.l   INT_HInterrupt		; IRQ level 4 (horizontal retrace interrupt)&lt;br /&gt;
	dc.l   INT_Null  			; IRQ level 5&lt;br /&gt;
	dc.l   INT_VInterrupt		; IRQ level 6 (vertical retrace interrupt)&lt;br /&gt;
	dc.l   INT_Null				; IRQ level 7&lt;br /&gt;
	dc.l   INT_Null				; TRAP #00 exception&lt;br /&gt;
	dc.l   INT_Null				; TRAP #01 exception&lt;br /&gt;
	dc.l   INT_Null				; TRAP #02 exception&lt;br /&gt;
	dc.l   INT_Null				; TRAP #03 exception&lt;br /&gt;
	dc.l   INT_Null				; TRAP #04 exception&lt;br /&gt;
	dc.l   INT_Null				; TRAP #05 exception&lt;br /&gt;
	dc.l   INT_Null				; TRAP #06 exception&lt;br /&gt;
	dc.l   INT_Null				; TRAP #07 exception&lt;br /&gt;
	dc.l   INT_Null				; TRAP #08 exception&lt;br /&gt;
	dc.l   INT_Null				; TRAP #09 exception&lt;br /&gt;
	dc.l   INT_Null				; TRAP #10 exception&lt;br /&gt;
	dc.l   INT_Null				; TRAP #11 exception&lt;br /&gt;
	dc.l   INT_Null				; TRAP #12 exception&lt;br /&gt;
	dc.l   INT_Null				; TRAP #13 exception&lt;br /&gt;
	dc.l   INT_Null				; TRAP #14 exception&lt;br /&gt;
	dc.l   INT_Null				; TRAP #15 exception&lt;br /&gt;
	dc.l   INT_Null				; Unused (reserved)&lt;br /&gt;
	dc.l   INT_Null				; Unused (reserved)&lt;br /&gt;
	dc.l   INT_Null				; Unused (reserved)&lt;br /&gt;
	dc.l   INT_Null				; Unused (reserved)&lt;br /&gt;
	dc.l   INT_Null				; Unused (reserved)&lt;br /&gt;
	dc.l   INT_Null				; Unused (reserved)&lt;br /&gt;
	dc.l   INT_Null				; Unused (reserved)&lt;br /&gt;
	dc.l   INT_Null				; Unused (reserved)&lt;br /&gt;
	dc.l   INT_Null				; Unused (reserved)&lt;br /&gt;
	dc.l   INT_Null				; Unused (reserved)&lt;br /&gt;
	dc.l   INT_Null				; Unused (reserved)&lt;br /&gt;
	dc.l   INT_Null				; Unused (reserved)&lt;br /&gt;
	dc.l   INT_Null				; Unused (reserved)&lt;br /&gt;
	dc.l   INT_Null				; Unused (reserved)&lt;br /&gt;
	dc.l   INT_Null				; Unused (reserved)&lt;br /&gt;
	dc.l   INT_Null				; Unused (reserved)&lt;br /&gt;
&lt;br /&gt;
	; Example 2: For sizecoding purposes, we only specify the initial stack pointer&lt;br /&gt;
	; and the reset vector, the rest of the space we can use as executable code space.&lt;br /&gt;
	; In theory this should work, in non TMSS systems. In practice TMSS compatibility should&lt;br /&gt;
	; be taken into account (read &amp;quot;TMSS&amp;quot; for more information) and some emulators might not like this.&lt;br /&gt;
	dc.l   0x00FFE000			; Initial stack pointer value&lt;br /&gt;
	dc.l   CPU_EntryPoint		; Start of program&lt;br /&gt;
CPU_EntryPoint:&lt;br /&gt;
	&amp;lt;our demo&amp;gt;&lt;br /&gt;
	bra.s	CPU_EntryPoint&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This vector table is then followed by the ROM header at 0x100&lt;br /&gt;
&lt;br /&gt;
=== ROM Header ===&lt;br /&gt;
The ROM Header is a data structure located at 0x000100 that specifies some metadata about the ROM; like the original and localized titles of the game, author and copyright, cart serial number, region, some information about which peripherals the game will use, Save RAM mapping ranges, ROM mapping ranges, and most importantly, the system type.&lt;br /&gt;
If you want to read the full structure of the ROM Header, head to https://plutiedev.com/rom-header&lt;br /&gt;
&lt;br /&gt;
Mega Drive systems prior to probably the Mega Drive 1 VA5 or VA4 can boot cartridges that contain absolutely no ROM Header, as these consoles simply ignore it. You could stop there and just ditch the ROM Header entirely, but later Mega Drive 1's, Mega Drive 2's and Mega Drive 3's all come equipped with a trademark challenge-response type protection scheme that makes this not that simple. This is called the TradeMark Security System, or TMSS.&lt;br /&gt;
&lt;br /&gt;
=== TMSS ===&lt;br /&gt;
TMSS is an onboard 16KB ROM that gets mapped to the cartridge's address space when the cosole first turns on. The protection scheme has two stages:&lt;br /&gt;
&lt;br /&gt;
* First, and skipping a few minor details, TMSS uploads some code to RAM, jumps to said code and then unmaps itself and maps the cartridge into the address space. The RAM code then scans the cartridge's ROM Header for the system type. All it wants is to find the string &amp;quot;SEGA&amp;quot; or &amp;quot; SEGA&amp;quot; (to account for a typo, maybe?), if the SEGA string is found at address 0x000100, TMSS proceeds to the second stage, if not, it unmaps the cartridge and maps itself back, exits that RAM routine and locks up the system.&lt;br /&gt;
&lt;br /&gt;
TMSS then maps itself back, uploads some palettes to CRAM, uploads tiles and a simple tilemap to VRAM and runs a timer. This is to show the following message on screen: &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;&amp;quot;&amp;gt;&lt;br /&gt;
   PRODUCED BY OR&lt;br /&gt;
 UNDER LICENSE FROM&lt;br /&gt;
SEGA ENTERPRISES LTD.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* In the second stage, when the timer runs out, TMSS disables display by reseting the VDP's MODE2 register, but it doesn't clear VRAM or CRAM, so a few colors and assets reside in video memory after control has been passed to the cartridge.&lt;br /&gt;
&lt;br /&gt;
It then locks the VDP, maps the cartridge, initializes the cartridge's stack pointer and jumps to its reset vector. TMSS locks the VDP so the cartridge has to write the string &amp;quot;SEGA&amp;quot; at $A14000 before trying to access any video registers, otherwise the VDP won't assert /DTACK when the 68k tries to read or write to its ports, effectively locking up the console.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
So, all you need to ensure your demo has full TMSS compatibility is have the string &amp;quot;SEGA&amp;quot; at 0x000100 and make your demo write &amp;quot;SEGA&amp;quot; at $A14000 before accessing any VDP port. Of course, this would make true 256 byte demos impossible, since you need those trailing 4 bytes for the &amp;quot;SEGA&amp;quot; string. What we can do is leverage ROM mirroring, and put the &amp;quot;SEGA&amp;quot; string where the stack vector would be. If we make a simple EEPROM cartridge and cut the address lines to the 256 byte range, TMSS would read 0x000100 as 0x000000.&lt;br /&gt;
&lt;br /&gt;
There is a problem with emulators, however. Emulators usually load ROMs differently depending on the system type, but as we don't have one (we just care about the &amp;quot;SEGA&amp;quot; string) they will probably default to loading it as if it were a flat &amp;lt; 4MB cartridge. If emulators load ROMs in a flat 4MB buffer, the following parts of the address space that we expect to be mirrored might contain all zeros or garbage. To prevent this, the safest thing to do is to emulate a non TMSS system when running the demo on an emulator. BlastEm, for example, emulates non TMSS systems by default, but lets you choose.&lt;br /&gt;
&lt;br /&gt;
=== Memory map ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;&amp;quot;&amp;gt;&lt;br /&gt;
Start address 	End address 	Description &lt;br /&gt;
$000000 		$3FFFFF 		Cartridge ROM/RAM &lt;br /&gt;
$400000 		$7FFFFF 		Reserved (used by the Mega-CD and 32X)&lt;br /&gt;
$800000 		$9FFFFF 	    Reserved (used by the 32X)&lt;br /&gt;
$840000 		$85FFFF 	    32X frame buffer 	&lt;br /&gt;
$860000 		$87FFFF 	    32X frame buffer overwrite mode &lt;br /&gt;
$880000 		$8FFFFF 	    32X cartridge ROM (first 512kB bank only)&lt;br /&gt;
$900000 	    $9FFFFF 	    32X cartridge bankswitched ROM (any 512kB bank, controlled by 32X registers)&lt;br /&gt;
$A00000 	    $A0FFFF 	    Z80 memory space&lt;br /&gt;
$A10000 	    $A10001 	    Version register&lt;br /&gt;
$A10002 	    $A10003 	    Controller 1 data&lt;br /&gt;
$A10004 	    $A10005 	    Controller 2 data&lt;br /&gt;
$A10006 	    $A10007 	    Expansion port data&lt;br /&gt;
$A10008 	    $A10009 	    Controller 1 control&lt;br /&gt;
$A1000A 	    $A1000B 	    Controller 2 control&lt;br /&gt;
$A1000C 	    $A1000D 	    Expansion port control&lt;br /&gt;
$A1000E 	    $A1000F 	    Controller 1 serial transmit&lt;br /&gt;
$A10010 	    $A10011 	    Controller 1 serial receive&lt;br /&gt;
$A10012 	    $A10013 	    Controller 1 serial control&lt;br /&gt;
$A10014 	    $A10015 	    Controller 2 serial transmit&lt;br /&gt;
$A10016 	    $A10017 	    Controller 2 serial receive&lt;br /&gt;
$A10018 	    $A10019 	    Controller 2 serial control&lt;br /&gt;
$A1001A 	    $A1001B 	    Expansion port serial transmit&lt;br /&gt;
$A1001C 	    $A1001D 	    Expansion port serial receive&lt;br /&gt;
$A1001E 	    $A1001F 	    Expansion port serial control&lt;br /&gt;
$A10020 	    $A10FFF 	    Reserved&lt;br /&gt;
$A11000 	                    Memory mode register&lt;br /&gt;
$A11002 	    $A110FF 	    Reserved&lt;br /&gt;
$A11100 	    $A11101 	    Z80 bus request&lt;br /&gt;
$A11102 	    $A111FF 	    Reserved&lt;br /&gt;
$A11200 	    $A11201 	    Z80 reset&lt;br /&gt;
$A11202 	    $A12FFF 	    Reserved&lt;br /&gt;
$A13000 	    $A130FF 	    TIME registers; used to send signals to the cartridge&lt;br /&gt;
$A130EC 	    $A130EF 	    &amp;quot;MARS&amp;quot; when 32X is attached&lt;br /&gt;
$A130F1 	                    SRAM access register&lt;br /&gt;
$A130F3 	                    Bank register for address $80000-$FFFFF&lt;br /&gt;
$A130F5 	                    Bank register for address $100000-$17FFFF&lt;br /&gt;
$A130F7 	                    Bank register for address $180000-$1FFFFF&lt;br /&gt;
$A130F9 	                    Bank register for address $200000-$27FFFF&lt;br /&gt;
$A130FB 	                    Bank register for address $280000-$2FFFFF&lt;br /&gt;
$A130FD 	                    Bank register for address $300000-$37FFFF&lt;br /&gt;
$A130FF 	                    Bank register for address $380000-$3FFFFF&lt;br /&gt;
$A14000 	    $A14003 	    TMSS &amp;quot;SEGA&amp;quot;&lt;br /&gt;
$A14101 	                    TMSS/cartridge register&lt;br /&gt;
$A14102 	    $BFFFFF 	    Reserved&lt;br /&gt;
$C00000 	    $C00001 	    VDP data port&lt;br /&gt;
$C00002 	    $C00003 	    VDP data port (mirror)&lt;br /&gt;
$C00004 	    $C00005 	    VDP control port&lt;br /&gt;
$C00006 	    $C00007 	    VDP control port (mirror)&lt;br /&gt;
$C00008 	    $C00009 	    VDP H/V counter&lt;br /&gt;
$C0000A 	    $C0000F 	    VDP H/V counter (mirror)&lt;br /&gt;
$C00011 	                    PSG output&lt;br /&gt;
$C00013 	    $C00017 	    PSG output (mirror)&lt;br /&gt;
$C0001C 	    $C0001D 	    Debug register&lt;br /&gt;
$C0001E 	    $C0001F 	    Debug register (mirror)&lt;br /&gt;
$C00020 	    $FEFFFF 	    Reserved&lt;br /&gt;
$FF0000 	    $FFFFFF 	    68000 RAM &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Startup ===&lt;br /&gt;
&lt;br /&gt;
Here it's described what things you should do first in your ROM, and the state to expect from the console when it first turns on.&lt;br /&gt;
&lt;br /&gt;
In a TMSS system, when control is passed to the cartridge, both horizontal and vertical interrupts will be disabled from the VDP side, DMA will be disabled, there will be leftover graphics and colors from the TMSS screen and display rendering will be turned off. In a non TMSS system state is pretty much random, main memory and video memory won't be initialized and the VDP could be in virtually any random state. Make sure to also read the &amp;quot;Interrupts&amp;quot; section in &amp;quot;VDP&amp;quot; below.&lt;br /&gt;
&lt;br /&gt;
To ensure predictable state on any console, you should first write &amp;quot;SEGA&amp;quot; at $A14000 to unlock the VDP, test the VDP control port to make it return to a known state and enable display rendering. Some of the steps (like the hardware version check before writing &amp;quot;SEGA&amp;quot;) and testing VDP control could be removed, but bugs might arise in some console revisions.&lt;br /&gt;
&lt;br /&gt;
A simple ROM that sets a background color and assumes ROM mirroring:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;&amp;quot;&amp;gt;&lt;br /&gt;
        dc.l    &amp;quot;SEGA&amp;quot;&lt;br /&gt;
        dc.l    start&lt;br /&gt;
&lt;br /&gt;
start:&lt;br /&gt;
        ; Test hardware rev. Skipping this and blindly writing to $A14000&lt;br /&gt;
        ; regardless of the console revision could work, but not guaranteed&lt;br /&gt;
        move.w  $A10000, ccr&lt;br /&gt;
        bcc.s   @skip_tmss&lt;br /&gt;
        move.l  sp, ($A14000)           ; Unlock VDP with &amp;quot;SEGA&amp;quot;. sp contains &amp;quot;SEGA&amp;quot;&lt;br /&gt;
@skip_tmss:&lt;br /&gt;
        lea     ($C00004), a0&lt;br /&gt;
        ; Stop VDP / Return VDP to known state. Skipping this could&lt;br /&gt;
        ; result in the VDP processing whatever was previously in its FIFO after a reset&lt;br /&gt;
        tst.w   (a0)&lt;br /&gt;
                                        &lt;br /&gt;
        move.l  #$80048F00, (a0)        ; Set bit 2 of MODE1, and set Auto Increment to 0&lt;br /&gt;
        move.l  #$8144C000, (a0)        ; Set bit 2 of MODE2, enable display, and point data port to CRAM&lt;br /&gt;
        move.w  #$0F00, -4(a0)          ; Write blue at color index 0 of CRAM&lt;br /&gt;
        bra.s   *&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== VDP ===&lt;br /&gt;
&lt;br /&gt;
The Video Display Processor (VDP) is at the heart of graphics (and some sound) features of the Mega Drive. At first glance it might seem like a modest tile based video controller but it has tricks up its sleeve. It is assumed that you are familiar with VDP registers. If not, this resource is very handy: https://plutiedev.com/vdp-registers&lt;br /&gt;
&lt;br /&gt;
You should also know what ports to use and how to write to these various registers: https://plutiedev.com/vdp-setup&lt;br /&gt;
&lt;br /&gt;
==== Screen Resolutions ====&lt;br /&gt;
&lt;br /&gt;
The VDP always initializes to a 320x224 pixel resolution, but on PAL systems a higher vertical resolution of 240 pixels is selectable through the MODE2 register. Bit 3 selects either V28 (224px) or V30 (240px) vertical resolutions.&lt;br /&gt;
&lt;br /&gt;
Available on all systems regardless of region is the possibility of choosing a narrower horizontal resolution of 256 pixels as well. This is done through the MODE4 register, where if bits 7 and 0 are both clear, H32 mode or 256 pixel horizontal resolution is selected. If both bits are set instead, the standard H40 mode or 320 pixel horizontal resolution is selected.&lt;br /&gt;
&lt;br /&gt;
===== Very important thing about H32 =====&lt;br /&gt;
&lt;br /&gt;
The native horizontal resolution of the VDP is 320 pixels, as we've said, how does it achieve a 256 pixel horizontal resolution in H32 mode, then? By underclocking its pixel clock.&lt;br /&gt;
&lt;br /&gt;
This means that when you select a horizontal resolution of 256 pixels the VDP runs slower, since a lot of its internal logic is dependent on the pixel clock. One very important result that you'll have to face if you select this mode is that the maximum sprite amount per frame will be reduced from 80 (in H40) to 64 (in H32). Coincidentally, the maximum sprite amount per scanline will also be reduced from 20 to 16. This is simply because the VDP will run at a slower pixel clock and it won't be fast enough to fetch all the sprites it's capable of rendering during every blanking period.&lt;br /&gt;
&lt;br /&gt;
==== Sprites ====&lt;br /&gt;
&lt;br /&gt;
Sprites are freely moving objects independent from the 2 background planes. They can be configured to be any size ranging from 1x1 tile to 4x4 tiles (8x8 px tiles, so 8x8 to 32x32 px). Any combination of width and height as long as it's within the 1x1 to 4x4 limit is allowed.&lt;br /&gt;
&lt;br /&gt;
Sprites reside in VRAM as a sort of display list. Every sprite field has x,y position information, tile index for where to find the first tile of the sprite in VRAM, horizontal and vertical flipping, which palette (PAL 0, 1, 2, or 3) to use and finally the &amp;quot;link&amp;quot; value. The link value is used to tell the VDP which sprite in the Sprite Attribute Table (SAT) to process next. The VDP will always start processing the first sprite slot found in the SAT, then it reads the link value for the next sprite and jumps to its slot. Sprite processing ends when the VDP finds a sprite with a link value of 0.&lt;br /&gt;
&lt;br /&gt;
Sprites can be multiplexed (i.e. change their possition and attributes mid frame) to surpass the maximum amount of sprites per frame. Beware that the VDP caches some of the sprite attributes, however. For every sprite fetch it caches the Y coordinate and size of the sprite. The cache is flushed by writing to the Sprite Attribute Table in VRAM, but not if the table's address is swapped mid frame, so if you have two Sprite Attribute Tables and just swap from one to the other mid frame to change sprite behavior on the fly you'll end up with half the data from the previous table affecting your new sprites.&lt;br /&gt;
&lt;br /&gt;
More information about sprite definition and caching can be found here: https://plutiedev.com/sprites&lt;br /&gt;
&lt;br /&gt;
==== Background planes ====&lt;br /&gt;
&lt;br /&gt;
The VDP can generate 2 planes of tile based backgrounds. Each tile in the tilemap(s) can reference a different 16 color palette, and each background plane can be independently scrolled in a few different ways:&lt;br /&gt;
&lt;br /&gt;
* Each plane can be scrolled completely. Or scrolled in &amp;quot;full plane&amp;quot;.&lt;br /&gt;
* Each plane can be scrolled per individual vertical column. Each column is 16 pixels in width. This is done by writing to VSRAM (vertical scroll RAM). Each word is a different scroll position for each column on each plane.&lt;br /&gt;
* Each plane can be scrolled per individual horizontal row of 8 pixels in height. Think of it as scrolling &amp;quot;one row of tiles&amp;quot;. This is done by writing to HSCROLL, a horizontal scroll table similar to VSRAM but held in VRAM.&lt;br /&gt;
* Each plane can be scrolled per individual horizontal line. Yes, you can do scanline independent scrolling without interrupts. This is also through the HSCROLL table. &lt;br /&gt;
&lt;br /&gt;
Both planes can be either 32x32, 32x64, 64x32, 64x64, 128x64 or 64x128 tiles in size. You cannot select different sizes for each plane. If you select 32x32, both planes will be 32x32 tiles in size.&lt;br /&gt;
&lt;br /&gt;
==== Interrupts ====&lt;br /&gt;
&lt;br /&gt;
This is important for sizecoding purposes. The VDP generates horizontal interrupts, vertical interrupts and &amp;quot;external&amp;quot; interrupts. External interrupts are for lightguns and such peripherals.&lt;br /&gt;
&lt;br /&gt;
Interrupts are enabled from two sides:&lt;br /&gt;
* From the interrupt mask set in the 68k&lt;br /&gt;
* From the VDP registers&lt;br /&gt;
&lt;br /&gt;
It's impossible to get an interrupt on startup unless you enable it from both sides, because even if interrupts were to be enabled through VDP registers, the 68k always initializes to IPL level 7 on startup, so all interrupts are ignored.&lt;br /&gt;
&lt;br /&gt;
To implement raster effects without interrupts, you can poll the VDP's HVCOUNT port ($C00008). You can get an approximate of what line the VDP is currently rendering there.&lt;br /&gt;
&lt;br /&gt;
To get VBlank state you can poll the VDP's status register (reading from VDP control, or $C00004) and check the VBlank flag.&lt;br /&gt;
&lt;br /&gt;
=== Additional Resources ===&lt;br /&gt;
* [https://demozoo.org/productions/?platform=22 SEGA MegaDrive demoscene productions]&lt;br /&gt;
* [https://plutiedev.com/ Plutiedev. Lots of info about any aspect of the system. This is more than enough to get you started]&lt;br /&gt;
* [https://plutiedev.com/mirror/kabuto-hardware-notes SEGA MegaDrive Hardware notes]&lt;br /&gt;
* [https://www.scribd.com/document/637611089/SEGA-Mega-Drive-Assembly-Workshop SEGA MegaDrive Assembly Workshop]&lt;br /&gt;
* [https://github.com/BigEvilCorporation/megadrive_samples/tree/master Various SEGA MegaDrive assembler examples]&lt;br /&gt;
* [https://github.com/OrionNavattan/TMSS-Disassembly/blob/main/tmss.asm TMSS ROM disassembly]&lt;/div&gt;</summary>
		<author><name>Superogue</name></author>	</entry>

	<entry>
		<id>http://www.sizecoding.org/wiki/Sinclair_QL</id>
		<title>Sinclair QL</title>
		<link rel="alternate" type="text/html" href="http://www.sizecoding.org/wiki/Sinclair_QL"/>
				<updated>2025-12-06T13:22:32Z</updated>
		
		<summary type="html">&lt;p&gt;Superogue: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Sinclair QL ==&lt;br /&gt;
The Sinclair QL (for Quantum Leap) is a personal computer launched by Sinclair Research in 1984, as an upper-end counterpart to the ZX Spectrum.&lt;br /&gt;
It was aimed at the serious home user and professional and executive users markets from small to medium-sized businesses and higher educational establishments, but failed to achieve commercial success.&lt;br /&gt;
The Sinclair QL uses a Motorola 68008.CPU with 32-bit internal data registers, but an 8-bit external data bus.&lt;br /&gt;
&lt;br /&gt;
=== Setting up ===&lt;br /&gt;
* Assembler: VASM&lt;br /&gt;
* Emulator(s): Q-emulator ( http://www.terdina.net/ql/winql.html )&lt;br /&gt;
* Tool(s): Mdvtool (create MDV images)&lt;br /&gt;
* Hardware: Sinclair QL machine or Spectrum Next running the QL core&lt;br /&gt;
&lt;br /&gt;
In order to test the final result one can use a floppy-disk replace replacement or Qubide interface to load files from SD card.&lt;br /&gt;
&lt;br /&gt;
=== Loader ===&lt;br /&gt;
To load content from a folder or MDV image, you need both the binary and a basic loader, similar to platforms like the ZX Spectrum and CPC, create a file called BOOT in the output folder with the following code:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;&amp;quot;&amp;gt;&lt;br /&gt;
10 PROGRAM=RESPR(512)&lt;br /&gt;
15 LBYTES &amp;quot;MDV1_CODE&amp;quot;,PROGRAM&lt;br /&gt;
20 CALL PROGRAM&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now copy your intro code as CODE into the same output folder and Q-Emulator should be able to pick everything up automatically and load the code. You can link the folder directly from the Q-emulator or generate a MDV image using mdvtool for distribution.&lt;br /&gt;
&lt;br /&gt;
=== Memory map ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;&amp;quot;&amp;gt;&lt;br /&gt;
$00000 	Onboard 48k Rom 	&lt;br /&gt;
$0C000 	16K Rom Cartridge 	&lt;br /&gt;
$10000 	Onboard I/O 	&lt;br /&gt;
$18000 	(R) RTC byte 0 / (W) RTC Reset&lt;br /&gt;
$18001 	(R) RTC byte 1 / (W) RTC Step&lt;br /&gt;
$18002 	(R) RTC byte 2 / (W) Transmit control&lt;br /&gt;
$18003 	(R) RTC byte 3 / (W) IPC link control&lt;br /&gt;
$18020 	(R) Microdrive/RS232c status / (W) Microdrive control&lt;br /&gt;
$18021 	(R) Interrupt/IPC status / (W) Interrupt control&lt;br /&gt;
$18022 	(R) Microdrive Track 1 / (W) Microdrive / RS232C data&lt;br /&gt;
$18023 	(R) Microdrive Track 2 / (W) Display control&lt;br /&gt;
$18063 	Screen Mode S---C-O- On Colordepth Screenpage&lt;br /&gt;
$20000 	Screen 1 	Screen Ram&lt;br /&gt;
$28000 	Screen 2 /&lt;br /&gt;
System 	system (systemvars*)&lt;br /&gt;
$2847C 	System stack pointer*&lt;br /&gt;
$28E00 	Base of Common Heap*&lt;br /&gt;
$2BC00 	Free area*&lt;br /&gt;
$30000 	Running Programs 	Free area&lt;br /&gt;
$37200 	Basic area*&lt;br /&gt;
$38000 	User Stack pointer*&lt;br /&gt;
$38000 	Prog data*&lt;br /&gt;
$40000 	Add on ram (up to 512k) 	&lt;br /&gt;
$C0000 	Add on peripherals 	&lt;br /&gt;
$E0000 	Add on Rom (up to 128k) 	&lt;br /&gt;
$FFFFF 	End of address space&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Video display ===&lt;br /&gt;
There are two possible screen modes, configured by bit 3 of port $18063&lt;br /&gt;
&lt;br /&gt;
setting a 0 give 4 colors at 512x256 with Black,Red,Green and White&lt;br /&gt;
setting a 1 give 8 colors at 256x256 with Black,  R, G B, C, M, Y and White&lt;br /&gt;
&lt;br /&gt;
The Screen is directly memory mapped from $20000-$28000, There is no palette - the colors are fixed.&lt;br /&gt;
&lt;br /&gt;
==== Vsync ====&lt;br /&gt;
Port $18021 bit 3 will go high (1) when Vsync starts, then we need to write a 1 to that same bit at the same port to clear the Vsync event.&lt;br /&gt;
Therefore, in effect we can write 255 to port $18021, then read from $18021 until it's nonzero to get the Vsync event.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;&amp;quot;&amp;gt;&lt;br /&gt;
 move.b #%11111111, $18021     ; Clear interrupt bits&lt;br /&gt;
waitvbl:&lt;br /&gt;
 move.b $18021,d0            ; Read in interrupt state&lt;br /&gt;
 tst.b d0                    ; Wait for an interrupt&lt;br /&gt;
 beq waitvbl&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Before using Screen2 ====&lt;br /&gt;
Since the Screen2 area is mapped to the system, we need to add the following code at the start of the program to be able&lt;br /&gt;
to make use of it.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;&amp;quot;&amp;gt;&lt;br /&gt;
 trap #0&lt;br /&gt;
 ori #0700,sr&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or alternatively move the stack manually &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;&amp;quot;&amp;gt;&lt;br /&gt;
 move.l  #$40000-4,sp&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Swapping screens ====&lt;br /&gt;
To swap between the two screens via hardware, we need to swap the buffer pointer and set the screen register accordingly.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;&amp;quot;&amp;gt;&lt;br /&gt;
resetScreens:&lt;br /&gt;
 move.l  #$20000,screenpointer1&lt;br /&gt;
 move.l  #$28000,screenpointer2&lt;br /&gt;
 move.b  #8,scr&lt;br /&gt;
 move.b  #0,scr+1&lt;br /&gt;
 move.b  #8,$18063&lt;br /&gt;
 rts&lt;br /&gt;
&lt;br /&gt;
swapscreens: &lt;br /&gt;
 lea  screenpointer1,a0 ; get screen ptr address&lt;br /&gt;
 move.l  (a0),d0         ; save it&lt;br /&gt;
 move.l  4(a0),(a0)+     ; rotate screenbuffers&lt;br /&gt;
 move.l  d0,(a0)         ; write back&lt;br /&gt;
 lea     scr,a0          ; get flipbit&lt;br /&gt;
 move.b  (a0),$18063     ; write flipbit&lt;br /&gt;
 eor.b   #128,(a0)       ; flip the bit&lt;br /&gt;
 rts&lt;br /&gt;
&lt;br /&gt;
scr: dc.b    8,0&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Copying and clearing the screen ====&lt;br /&gt;
As an alternative to swapping the screenpointers, here is some compact, but slow, code to copy over the contents from screen2 to screen1 and clear the screen2.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;&amp;quot;&amp;gt;&lt;br /&gt;
 lea $28000,a0       ; screen2&lt;br /&gt;
 lea $20000,a1       ; screen1	&lt;br /&gt;
 move.l a0,a2&lt;br /&gt;
 move.w  #256*32,d0&lt;br /&gt;
swappage: &lt;br /&gt;
 move.l   (a2),(a1)+&lt;br /&gt;
 clr.l   (a2)+&lt;br /&gt;
 dbf     d0,swappage&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Plotting to screen ====&lt;br /&gt;
While there are a few rom/trap routines that allow you to draw primitives, these are quite expensive to setup and clunky to use.&lt;br /&gt;
So its best to draw into the screenmemory directly, using the following plot routines provided by gigabates.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;&amp;quot;&amp;gt;&lt;br /&gt;
; plot pixel&lt;br /&gt;
; a0 = screen,  d0 = x, d1 = y, d2 = color index 0-7&lt;br /&gt;
plot:&lt;br /&gt;
 lsl.w   #6,d1           ; y byte&lt;br /&gt;
 moveq   #3,d3           ; x pixel shift&lt;br /&gt;
 and.w   d0,d3&lt;br /&gt;
 add.w   d3,d3&lt;br /&gt;
 asr.w   #2,d0           ; x byte&lt;br /&gt;
 add.w   d0,d1           ; total bytes&lt;br /&gt;
 add.w   d1,d1           ; to word offset&lt;br /&gt;
 lsl.b   #6,d2&lt;br /&gt;
 bcc     .noGreen&lt;br /&gt;
 add.w   #$8000,d2&lt;br /&gt;
.noGreen:&lt;br /&gt;
 lsr.w   d3,d2           ; shift scrambled color to px pos&lt;br /&gt;
 or.w    d2,(a0,d1)&lt;br /&gt;
 rts &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Sound ===&lt;br /&gt;
The Spectrum QL has 2 channels of internal beeped sound, or AY support via an extension board.&lt;br /&gt;
&lt;br /&gt;
Speaker Sound commands have to be passed via the Bios, using the same kind of commands as with the keyboard.&lt;br /&gt;
You need to adjust the Pitch settings to change the sound, and you can change the randomness bits to make the sound distorted, It seems it's not possible to change the volume!&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;&amp;quot;&amp;gt;&lt;br /&gt;
move.l #$11,d0			; set sound command&lt;br /&gt;
lea .sounddata,a3		; load sound message payload pointer to a3&lt;br /&gt;
trap #1				; call dosound&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Additional Resources ===&lt;br /&gt;
* [https://demozoo.org/productions/?platform=95 Sinclair QL demoscene productions]&lt;br /&gt;
* [https://ia600804.us.archive.org/9/items/SinclairQLHomepage/docs/manuals/qltm.pdf QL Technical Guide]&lt;br /&gt;
* [https://www.chibiakumas.com/68000/sinclairql.php 68008 Assembly programming for the Sinclair QL]&lt;br /&gt;
* [https://dilwyn.qlforum.co.uk/  Sinclair QL Pages]&lt;br /&gt;
* [http://www.terdina.net/ql/winql.html Q_emulator]&lt;/div&gt;</summary>
		<author><name>Superogue</name></author>	</entry>

	<entry>
		<id>http://www.sizecoding.org/wiki/Prototyping_DOS_effects_with_ShaderToy</id>
		<title>Prototyping DOS effects with ShaderToy</title>
		<link rel="alternate" type="text/html" href="http://www.sizecoding.org/wiki/Prototyping_DOS_effects_with_ShaderToy"/>
				<updated>2025-10-05T19:21:08Z</updated>
		
		<summary type="html">&lt;p&gt;Pestis: /* Vector arithmetic (examples) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Sometimes it is useful to prototype ideas for DOS effects before going through the trouble of writing them in x86/x87 assembly. [https://shadertoy.com/ Shadertoy] is a popular choice for creating such prototypes. However, the shaders are written in WebGL, which is a relatively powerful language and includes native support for vectors, matrices, many built-in functions, and arithmetic operations. Most of these features are not available in x86 assembly. It is fairly easy to write tiny shaders in Shadertoy that end up well over 256 bytes once finally ported to DOS. Furthermore, the x87's limit of 8 stack items imposes significant constraints on the number of temporary variables you can use: for example, two 3D vectors already occupy 6 stack slots, and you typically need at least one additional item as scratch space, so that's almost the entire stack already.&lt;br /&gt;
&lt;br /&gt;
To make sure your Shadertoy prototype is portable to DOS, you should avoid operations that are going to be costly (in terms of bytes) and only use ones that will be cheap in assembly. Below you’ll find some size estimates for WebGL code once ported to x87 math.&lt;br /&gt;
&lt;br /&gt;
== Scalar operators ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! ShaderToy !! Bytes !! x87 equivalent &lt;br /&gt;
|-&lt;br /&gt;
| x+=y || 2 || faddp st1, st0 &lt;br /&gt;
|-&lt;br /&gt;
| x+y || 4 || If both x and y are needed later:&amp;lt;br/&amp;gt;fld st0; fadd st0, st2&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The cost for &amp;lt;code&amp;gt;-&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;*&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;/&amp;lt;/code&amp;gt; scalar operations is identical. A lot of this depends on how your x87 stack is organized (which variable is at the top of the stack at st0) and whether you need to keep copies of the variables for later use. In the last optimization phases, you can often save a few bytes by reorganizing your stack, to avoid unnecessary &amp;lt;code&amp;gt;fld&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;fxch&amp;lt;/code&amp;gt; instructions.&lt;br /&gt;
&lt;br /&gt;
Notice the existence of &amp;lt;code&amp;gt;fsubr&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;fdivr&amp;lt;/code&amp;gt; instructions, so &amp;lt;code&amp;gt;x=(y/x)&amp;lt;/code&amp;gt; can still be just 2 bytes, even if it looks more complicated in ShaderToy.&lt;br /&gt;
&lt;br /&gt;
Also notice that operating on a single component of a vector (&amp;lt;code&amp;gt;b.x += a.x&amp;lt;/code&amp;gt;) is actually a scalar operation and thus takes the same 2-4 bytes.&lt;br /&gt;
&lt;br /&gt;
== Scalar functions ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! ShaderToy !! Bytes !! x87 equivalent &lt;br /&gt;
|-&lt;br /&gt;
| -x || 2 || fchs&lt;br /&gt;
|-&lt;br /&gt;
| abs(x) || 2 || fabs&lt;br /&gt;
|-&lt;br /&gt;
| sqrt(x) || 2 || fsqrt&lt;br /&gt;
|-&lt;br /&gt;
| sin(x) || 2 || fsin&lt;br /&gt;
|-&lt;br /&gt;
| cos(x) || 2 || fcos&lt;br /&gt;
|-&lt;br /&gt;
| sin(x) ... cos(x) || 2 || fsincos&lt;br /&gt;
|-&lt;br /&gt;
| tan(x) || 2 || fptan&lt;br /&gt;
|-&lt;br /&gt;
| atan(y,x) || 2 || fpatan&lt;br /&gt;
|-&lt;br /&gt;
| log2(x) || 4 || fld1&amp;lt;br&amp;gt;...&amp;lt;br&amp;gt;fyl2x&lt;br /&gt;
|-&lt;br /&gt;
| sign(x) || 6 || Computed as x/abs(x)&amp;lt;br/&amp;gt;fld st0; fabs; fdivp st1, st0&amp;lt;br/&amp;gt;Note that this does not handle the case x=0&lt;br /&gt;
|-&lt;br /&gt;
| mix(x,y,a) || 10 || Stack in: a x y. Stack out: x*(1-a)+y*a.&amp;lt;br/&amp;gt;fmul st2, st0; fld1; fsubrp st1, st0; fmulp st1, st0; faddp st1, st0&lt;br /&gt;
|-&lt;br /&gt;
| 2*min(x,y) || 10 || Computed as a+b-abs(a-b) i.e.&amp;lt;br/&amp;gt;fld st0; fsub st0, st2; fabs; fsubp st1, st0; faddp st1, st0&amp;lt;br/&amp;gt;Replace fsubp with faddp for 2*max(x,y)&lt;br /&gt;
|-&lt;br /&gt;
| min(x,y) || 11 || fcom st0, st1; fnstsw ax; sahf; jc .S; fxch st0, st1; .S: fstp st1, st0&amp;lt;br/&amp;gt;Replace jc with jnc for max(x,y)&lt;br /&gt;
|-&lt;br /&gt;
| exp2(y) || 14 || fld1; fld st1; fprem; f2xm1; faddp st1,st0; fscale; fstp st1&lt;br /&gt;
|-&lt;br /&gt;
| 2*clamp(x,-1,1) || 14 || Computed as abs(1+x) - abs(1-x) i.e.&amp;lt;br/&amp;gt;fld1; fadd st0, st1; fabs; fld1; fsub st0, st2; fabs; fsubp st1, st0&amp;lt;br/&amp;gt;You can use other constants in place of fld1&lt;br /&gt;
|-&lt;br /&gt;
| pow(x,y) || 16 || Computed as 2^(y*log2(x)) i.e. fyl2x, followed by the exp2(x) code&lt;br /&gt;
|-&lt;br /&gt;
| exp(y) || 18 || Computed as 2^(y*log2(e)) i.e. fldl2e and fmulp, followed by the exp2(y) code&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
There are no &amp;lt;code&amp;gt;acos&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;asin&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;sinh&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;cosh&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;tanh&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;asinh&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;acosh&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;atanh&amp;lt;/code&amp;gt; instructions on x87 and implementing them yourself is probably not worth your bytes. This is a pity, as &amp;lt;code&amp;gt;tanh&amp;lt;/code&amp;gt; is a classic &amp;quot;squash&amp;quot; function to get any number into -1 .. 1 range.&lt;br /&gt;
&lt;br /&gt;
Notice the significant byte cost of min and max operations, primarily due to the absence of fcomi and fmovcc instructions on older processors. This can catch people off guard, as many shaders use min/max extensively. For example, raymarchers often rely on them to compute shape unions. Avoiding unnecessary use of min and max can prevent a few headaches later.&lt;br /&gt;
&lt;br /&gt;
== Rounding and remainders ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! ShaderToy !! Bytes !! x87 equivalent &lt;br /&gt;
|-&lt;br /&gt;
| round(x) || 2 || frndint with the default rounding mode, which is nearest&lt;br /&gt;
|-&lt;br /&gt;
| mod(x,y) || 2 || fprem or fprem1. Notice they compute the remainder, not modulo, so they are the same only for positive values of x.&lt;br /&gt;
|-&lt;br /&gt;
| ceil(x) || 2-7 || Up to 5 bytes to setup the rounding mode with fldcw (RC field = 10, round up), followed by frndint&lt;br /&gt;
|-&lt;br /&gt;
| floor(x) || 2-7 || Up to 5 bytes to setup the rounding mode with fldcw (RC field = 01, round down), followed by frndint&lt;br /&gt;
|-&lt;br /&gt;
| trunc(x) || 2-7 || Up to 5 bytes to setup the rounding mode with fldcw (RC field = 11, round towards zero), followed by frndint&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Notice that &amp;lt;code&amp;gt;x-round(x)&amp;lt;/code&amp;gt; is a very compact way to do domain repetition for raymarchers.&lt;br /&gt;
&lt;br /&gt;
== Vector arithmetic (examples) ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! ShaderToy !! Bytes !! x87 equivalent &lt;br /&gt;
|-&lt;br /&gt;
|| a.xy = a.yx || 2 || fxch st0, st1&lt;br /&gt;
|-&lt;br /&gt;
|| a.xyz = a.yzx || 4 || fxch st0, st2; fxch st0, st1;&lt;br /&gt;
|-&lt;br /&gt;
|| a+=b || 5-6 || Assuming b is not needed later.&amp;lt;br/&amp;gt;6 bytes: faddp st3, st0; faddp st3, st0; faddp st3, st0;&amp;lt;br/&amp;gt;5 bytes: if you have a trashable register with suitable parity, use the [[General_Coding_Tricks#Looping_three_times|looping three times trick]]&lt;br /&gt;
|-&lt;br /&gt;
|| dot(a,b) || 9-10 || If neither a or b is needed later, compute this as a*=b followed by a.z+=a.y+=a.x&lt;br /&gt;
|-&lt;br /&gt;
|| a+=b || 10 || If b is needed later: fadd st3; fld st1; faddp st5; fld st2; faddp st6&lt;br /&gt;
|-&lt;br /&gt;
|| length(a) || 14 || If a is not needed later: fmul st0, st0; fxch st0, st2; fmul st0, st0; faddp st2, st0; fmul st0, st0; faddp st1, st0; fsqrt&lt;br /&gt;
|-&lt;br /&gt;
|| mix(a,b,k) || 21-22 || Stack in: k a.x a.y a.z b.x b.y b.z&amp;lt;br/&amp;gt;fmul st4, st0; fmul st5, st0; fmul st6, st0; fld1; fsubrp st1, st0; fmul st1, st0; fmul st2, st0; fmulp st3, st0; faddp st3, st0; faddp st3, st0; faddp st3, st0&amp;lt;br/&amp;gt;For the last three repeating instructions, you might be able to use the [[General_Coding_Tricks#Looping_three_times|looping three times trick]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From this you can already see that a simple &amp;lt;code&amp;gt;normalize(a)&amp;lt;/code&amp;gt; is going to take a lot of bytes, as it has to be computed as &amp;lt;code&amp;gt;a/=length(a)&amp;lt;/code&amp;gt;. Therefore, normalizing your raymarchers rays is usually to be avoided. &amp;lt;code&amp;gt;cross&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;reflect&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;refract&amp;lt;/code&amp;gt; are probably also too costly for sizecoding.&lt;br /&gt;
&lt;br /&gt;
== Floating point constants ==&lt;br /&gt;
&lt;br /&gt;
x87 has the following constants built-in and loading each takes just 2 bytes:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Constant !! Approximation !! Instruction&lt;br /&gt;
|-&lt;br /&gt;
| 0.0 || 0.0 || fldz&lt;br /&gt;
|-&lt;br /&gt;
| 1.0 || 1.0 || fld1&lt;br /&gt;
|-&lt;br /&gt;
| pi || 3.14159... || fldpi&lt;br /&gt;
|-&lt;br /&gt;
| log2(e) || 1.44270... || fldl2e&lt;br /&gt;
|-&lt;br /&gt;
| loge(2) || 0.69315... || fldln2&lt;br /&gt;
|-&lt;br /&gt;
| log2(10) || 3.32193... || fldl2t&lt;br /&gt;
|-&lt;br /&gt;
| log10(2)|| 0.30103... || fldlg2&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Thus, if you just need &amp;quot;some random constant&amp;quot; in your shader, using one of these can save bytes. Notice, however, that &amp;lt;code&amp;gt;fldpi; fmulp st1, st0&amp;lt;/code&amp;gt; is still 4 bytes, whereas &amp;lt;code&amp;gt;fmul st0, dword [bp+offset]&amp;lt;/code&amp;gt; can be as little as 3 bytes, if the offset is short and you can reuse code or another value as the constant.&lt;br /&gt;
&lt;br /&gt;
Even if you need to define a new constant, you don't always need the full 4 bytes to define a single IEEE floating-point number—sometimes even a single byte suffices. With a single byte, you can already define the exponent of a float, so the order of magnitude is already correct. You can then try to place this somewhere in your code or data where at least the first few bits of the mantissa are correct, to slightly increase the accuracy. You can use tools like [https://www.h-schmidt.net/FloatConverter/IEEE754.html this] to see what floating-point values encode to, and what different byte patterns represent as floating-point constants.&lt;br /&gt;
&lt;br /&gt;
== Case study: Balrog == &lt;br /&gt;
&lt;br /&gt;
With all the earlier in mind, [https://github.com/vsariola/balrog/ Balrog] 256b executable graphics can serve as a case study. Balrog is a fractal raymarcher, with the innermost loop of:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
for(int j=0;j&amp;lt;ITERS;j++){                    &lt;br /&gt;
    t.x = abs(t.x - round(t.x)); // abs is folding, t.x - round(t.x) is domain repetition               &lt;br /&gt;
    t.x += t.x; // domain scaling&lt;br /&gt;
    r *= RSCALE;          &lt;br /&gt;
    r += t.x*t.x;&lt;br /&gt;
    t.xyz = t.yzx; // shuffle coordinates so next time we operate on previous y etc.&lt;br /&gt;
    t.x += t.z * o; // rotation, but using very poor math&lt;br /&gt;
    t.z -= t.x * o;               &lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Even if there's vectors, the code mostly does scalar math, and then uses coordinate shuffling (t.xyz = t.yzx) to do math on other coordinates. That code ports to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    mov     cl, ITERS&lt;br /&gt;
.maploop:                ; for(int j=0;j&amp;lt;ITERS;j++) {&lt;br /&gt;
    fld     st0&lt;br /&gt;
    frndint&lt;br /&gt;
    fsubp   st1, st0&lt;br /&gt;
    fabs                 ; t.x = abs(t.x - round(t.x))&lt;br /&gt;
    fadd    st0          ; t.x += t.x;&lt;br /&gt;
    fld     dword [c_rscale+bp-BASE]&lt;br /&gt;
    fmulp   st4, st0     ; r *= RSCALE&lt;br /&gt;
    fld     st0&lt;br /&gt;
    fmul    st0&lt;br /&gt;
    faddp   st4, st0     ; r += t.x*t.x&lt;br /&gt;
    fxch    st2, st0&lt;br /&gt;
    fxch    st1, st0     ; t.xyz = t.yzx&lt;br /&gt;
    fld     st2&lt;br /&gt;
    fmul    dword [si]&lt;br /&gt;
    faddp   st1, st0     ; t.x += t.z * o;&lt;br /&gt;
    fld     st0&lt;br /&gt;
    fmul    dword [si]&lt;br /&gt;
    fsubp   st3, st0     ; t.z -= t.x * o&lt;br /&gt;
    loop    .maploop     ; }&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The comments show exactly how each ShaderToy line maps to different x87 instructions.&lt;br /&gt;
&lt;br /&gt;
The Balrog code also later exemplifies the floating point truncation technique:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
c_mindist equ $-3&lt;br /&gt;
    db      0x38  ; 0.0001&lt;br /&gt;
c_glowamount equ $-2&lt;br /&gt;
c_colorscale equ $-2&lt;br /&gt;
    dw      0x3d61  ; 0.055&lt;br /&gt;
c_stepsizediv equ $-1&lt;br /&gt;
    db      0x03 ; 807&lt;br /&gt;
c_stepsizediv_z equ $-3&lt;br /&gt;
    db      0x40 ; 2.1006666666666662&lt;br /&gt;
c_glowdecay equ $-2&lt;br /&gt;
    dw      0x461c ; 1e4&lt;br /&gt;
c_rscale equ $-2&lt;br /&gt;
    db      0xa1, 0x3f  ; 1.2599210498948732&lt;br /&gt;
c_rdiv equ $-2&lt;br /&gt;
    dw      0x434b ; 203.18733465192963&lt;br /&gt;
c_camz equ $-1&lt;br /&gt;
    db      0xcc, 0x12, 0x42 ; 36.7&lt;br /&gt;
c_xdiv equ $-1&lt;br /&gt;
    db      0x09, 0x00, 0x40 ; 2.0006&lt;br /&gt;
c_xmult equ $-2&lt;br /&gt;
    dw      0x3f2a&lt;br /&gt;
c_camy equ $-2&lt;br /&gt;
    dw      0x3f1c ; 0.61&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Two of the constants were finally the same constant (c_glowamount and c_colorscale), many are only have the exponents (single db), and two of the constants required as much as 3 bytes to get enough precision (c_camz and c_xdiv). The ordering of the constants was carefully chosen, so that when the exponent of one constant serves as a part of the mantissa of next constant, the value is at least roughly correct.&lt;/div&gt;</summary>
		<author><name>Pestis</name></author>	</entry>

	</feed>