Copper Bars

BlitzMax Forums/BlitzMax Programming/Copper Bars

zoqfotpik(Posted 2014) [#1]
The Amiga was noted for having crazy background effects made up of horizontal bars at negligible CPU cost.
The hardware subsystem for doing this was called COPR for "color coprocessor" or something like that, aka Copper.
People did all sorts of things with it. Here's a partial emulation of it. interesting because it uses a very simple and short assembly language.

Some guys don't see the point of doing it this way but if you want to emulate Amiga background effects easily, this is the way to do it.

Maybe those guys don't see the point because they are sane.

The great thing about doing it this way is the fact that the copperlist pseudoprograms are very easy to store as data in external files.
Come to think of it I guess it's almost like a very primitive shader but instead of working by pixel it works by raster. Hey, they worked with what they had in the 1980s.

This takes less than 1 millisecond per frame on my rig.

For this little demo the distance of MouseY() away from the center of the screen controls cycling speed.

Graphics 1024,768
Global HEIGHT = 768
Global WIDTH = 1024
Global time:Int
Global Copper:copperlist = New copperlist ' Set up for drawing colorbars

Type Tcolor
	Field r:Int, g:Int, b:Int
	Method setme(rr:Int,gg:Int,bb:Int)
		r=rr
		g=gg
		b=bb
	End Method
	Method setcolorto()
		SetColor r,g,b
	End Method
End Type

Type copperlist
	Field numbars:Int = 255 ' Number of horizontal bars.  The screen is divided by this to get barheight.
	Field barheight:Float=3  ' Should be calculated dynamically on new() but is resolving to 1-- should be HEIGHT/numbars
	Field inst:Int[1000] ' list of instructions in copperlist.
	' Instructions can be either 0 or 1.  	0 means "wait until scanline [argument]."
	'										1 means "Setcolor to color in colorlist at array element [argument]."
	Field arg:Int[1000]	 ' list of arguments corresponding to instructions

	Field colorlist:tcolor[256]
	Field instpointer:Int ' pointer to current copper instruction
	Field waituntil:Int
	Field numinst:Int = 0
	Field coloroffset:Int = 0 ' number used for color cycling
	Field modfactor:Int = 127  ' Number to mod the cycling offset by
	
	Method draw()
		instpointer = 0
		For i = 0 To numbars
		If i > waituntil-1 
			instpointer = instpointer + 1
			If inst[instpointer]= 0 waituntil = arg[instpointer]  ' Wait until [arg]
			If inst[instpointer]= 1 colorlist[(Abs(arg[instpointer]+coloroffset)) Mod modfactor].setcolorto()  ' Change color to the color in colorlist[arg]
			If inst[instpointer]= 2 And i > arg[instpointer] instpointer = instpointer+1  ' If scanline is past [arg], skip next instruction
		EndIf 
		DrawRect 0,i*barheight, WIDTH,(i*barheight)+barheight ' Draw a horizontal bar of the correct size
		Next
	End Method
	
	Method setcoloritem(i:Int,r:Int, g:Int, b:Int)  ' Set a color item in the colorlist to a specified rgb
		colorlist[i].r = r
		colorlist[i].g = g
		colorlist[i].b = b
	End Method
	
	Method cycle(offset:Int)
		coloroffset = (coloroffset + offset) Mod 255   
		'If coloroffset < 0 coloroffset = coloroffset + 256
		'If coloroffset > 255 coloroffset = coloroffset - 256
		'Print coloroffset
	End Method
	
	Method addinstruction(instruction:Int,argument:Int)  ' Put an instruction and argument at the end of the instruction list
		If numinst<1000
			numinst = numinst+1
			inst[numinst]=instruction
			arg[numinst]=argument
		EndIf
	End Method
	
	Method New()
		'barheight = HEIGHT / numbars
		For i = 0 To 255
		' Initialize the colorlist to prevent exception from uninitialized fields
		colorlist[i]=New tcolor
		Next
	End Method
	
	Method printcopperlist()  ' Dump for debugging copperlist "programs"
		For i = 0 To numinst
			Print inst[i]+","+arg[i]
		Next
	End Method
	
End Type

Function Drawcopper()
		Copper.draw()
	End Function


For i = 0 To 127
Copper.setcoloritem(i,i+Sin(i*10)*127,127,127)  ' Building the color table
Copper.addinstruction(1,i)
Next
For i = 0 To 127
Copper.addinstruction(1,127-i)
Next

'Copper.printcopperlist()

While Not KeyDown(KEY_ESCAPE)
	'timer()
	Copper.cycle(HEIGHT/2 - MouseY())
	Drawcopper()
	'timer()
	Flip
Wend		

Function timer()
	If time > 0 
		Print "Millisecs elapsed since last call:" + ( MilliSecs()-time)
		time = 0
	Else time = MilliSecs()
	EndIf
End Function




Yasha(Posted 2014) [#2]
Not familiar at all with the technology, but this sounds very cool.

Is there a reference for the language? Perhaps it's something that can be JITted down to GLSL or AVX?


zoqfotpik(Posted 2014) [#3]
Behold:

http://amigadev.elowar.com/read/ADCD_2.1/Hardware_Manual_guide/node0047.html

Perhaps it's something that can be JITted down to GLSL or AVX?

Pretty sure it could.

Space Harrier is a great arcade example of what can be accomplished with this sort of subsystem:


Now the next thing I want to do is figure out how to do parallaxed checkerboards like you see on the floor above, pretty sure those can be done with horizontally tiled bar textures, that is one pixel tall, maybe even two pixel wide images stretched and tiled for parallax.

Yasha what I would like to see is two things: a generalized replacement for Cls() that would clear the screen with a drop-in shader, and then a postprocess shader that would take the current GLMax image and perform pixel operations on that to allow color cycling of a 256-item or longer color lookup table.

Imagine something like this instead of Cls() and how much visual razzmatazz it would add to an otherwise run-of-the-mill looking game:

http://glslsandbox.com/e#20859.0

Or this, holy mackerel! I mean seriously, WTF!?

http://glslsandbox.com/e#20800.0


Yasha(Posted 2014) [#4]
Oooohhhh I see so there are actually only the three instructions. I initially assumed this was something like ARB assembly language (which is a cool piece of history on its own). I guess "compiling" it wouldn't be very hard then...


Yasha what I would like to see is two things: a generalized replacement for Cls() that would clear the screen with a drop-in shader, and then a postprocess shader that would take the current GLMax image and perform pixel operations on that to allow color cycling of a 256-item or longer color lookup table.


Just to clarify: do you actually want something for practical, modern-style use in building new programs (building from an older technique), or were you investigating this primarily for historical value?


holy mackerel! I mean seriously, WTF!?


Shaders are awesome. It's a completely different way of thinking from the kind of serial programming we're used to... a better way of thinking.


zoqfotpik(Posted 2014) [#5]
>Just to clarify: do you actually want something for practical, modern-style use in building new programs (building from an older technique), or were you investigating this primarily for historical value?

The games I'm primarily interested in writing are not sidescrollers but pseudoisometric and I don't know how apropos these methods would be for that. I do work on sidescrollers but they are primarily technology tests.

I have a mostly finished sidescroller for mobiles but I don't know how well shaders work on phones and tablets. I'm also debating whether I should put any more work into it because I just don't know if there's a market, but on the other hand kids love splashy flashy stuff (it's a kid's platformer, about a cat.)

Shaders that did more than just horizontal bars might not be all that hard either. I'm sure we could add some more opcodes to that assembly language. Could we give it eg. an x and y register and a stack, like a 6502 assembly language that would compile to card machine language or even run in a supersimple VM on the card?

I've written small vm assembly languages before but never in a shader.


Yasha(Posted 2014) [#6]
Oh all modern tablets and phones are shader-only, same as desktops. There's no fixed-function hardware left out there any more.

Note that there's no portable, public "card machine language" in use at the moment. (ARB assembly never ran on the hardware - it's actually just an exceptionally clunky high-level language that still had to be compiled, usually to less efficient code than GLSL.) The GPU language is GLSL, which is compiled by the graphics driver.

You can compile any language you like to GLSL, of course (as long as it meets the linguistic restrictions imposed by the environment - it can never be Turing-complete; registers or a compile-time-checked stack are OK, dynamic/recursive stack is not). Implementing a VM interpreter in GLSL would be theoretically possible, but hopelessly slow (much worse than on the CPU) - dynamic recompilation would be a lot more effective. Interpretation on the GPU would only be useful if the image is the code, not to run uniform code over a static image.


zoqfotpik(Posted 2014) [#7]
Understood. Hmmmm. Do you think the Amiga copper emulation above would be possible to implement on a shader? Or possibly a combination of CPU and shader? What about cycling a palette?

I just got several books on GLSL, can you recommend any?


Yasha(Posted 2014) [#8]
I can't recommend a specific GLSL resource... the best way to learn it is probably to go back to the sandbox and play around until you have a feel for how it works.

The "trick" is simply to let yourself abandon sequential logic and start thinking in terms of mappings between sets of data, rather than operations on individual datums. This might be harder than it sounds, but it's also the only thing you need to understand, to fully "get it". Everything else (uniforms and varyings and lookups, oh my) is a detail.


Still reading about Copper. It sounds like it's a weird fusion of array- and sequential-programming that might not translate perfectly to GLSL. Unless you're actually interested in the historical aspect (and the fun that can be had there), it's probably not a good starting point for practical work.

I don't fully understand what is meant by "palette cycling" (saw the other thread+videos, didn't grok it). It potentially sounds like something that essentially stops existing as a problem in shader terms, since there's no looping per se going on any more... pixels just compute themselves based on their time/position/origin/target.


Uncle(Posted 2014) [#9]
There's a good explanation on how to do the Space Harrier type floors on this site... http://www.extentofthejam.com/pseudo/

Loved playing space harrier as kid :)


zoqfotpik(Posted 2014) [#10]
Thanks! I think I figured it out in my head but it'll be interesting to see how they do it.

Yasha the way cycling basically works is this... you have a map or associative array of 256 ints, each corresponding to an RGB value. Then you shift all or part of the RGB side of the map, so that the part that falls off the bottom is put up on top.

I'm just going to leave it for now, probably there are all sorts of wilder things I will think of when getting shaders figured out.


angros47(Posted 2014) [#11]
Color cycling is also the answer to the ultimate question in SC2 (worth 12,000,000 credits!):

Why did your bridge turn purple?

The answer (revealed in 2002) was:

XFormPLUT (GetColorMapAddress (SetAbsColorMapIndex (CommData.AlienColorMap, 1)), ONE_SECOND / 2);

Now you know.




zoqfotpik(Posted 2014) [#12]
Actually it was not our bridge but that of the Melnorme.


angros47(Posted 2014) [#13]
And who is the fot?


Xerra(Posted 2014) [#14]
I was assuming this would be multi-coloured but all I got was flickering shades of pink even when I put it on full screen. I then tinkered for a bit and changed this method inside the tcolor type to this:-

Method setcolorto()
        SetColor(Rand(255), Rand(255), Rand(255))
'	SetColor r,g,b
End Method


A bonus side effect of emulating the old C64 tape loading colour scheme. Not exactly my intention, and there's probably much smaller routines to do that, but it gave me a nostalgia trip anyway :)


zoqfotpik(Posted 2014) [#15]
That's strange, it's multicolored on my box. Green and pink.

My usual color type has a rand method.

The c64 was surprisingly powerful in some limited ways. People are still doing crazy things with them.