bogax Posted September 9, 2013 Share Posted September 9, 2013 (edited) The lines to be written I called ldatI put ldat into a single table becauseI thought it would be easier to countthrough bytes than select amongst tablesand I didn't want to duplicate the bitselection code for each table (since theaim is compression)So it counts by (playfield) rowsif the row is even we get the next twonibbles of pointers, mask for the correctbits and divide by 4 (leaving room to count4 bytes of the line) if the row is odd maskand multiply by 4Bits are selected by shifting a 1 in cbitsas a mask which is also the bit counter (forthe 8 bits in each byte)The 4 bytes of a line are counted by incrementingldat_ptr, masking for the lower two bits andchecking if they've rolled over to 0col, the playfield columns, is incremented foreach bit and reset to 0 each time we start a row8 bits/byte * 4 bytes/lineuntestededit oops had the current bit test wrongauxillary editrow and col reversed in pfpixelmoved col reset outside the byte loopfixed the line data data L4p $01, $21, $21, $21, $21, $21, $21, $21 $21, $21, $21, $21, $21, $21, $32, $45 $66, $65, $22, $32, $22, $32, $22, $32 $22, $32, $22, $32, $22, $32, $72, $32 $44, $86, $66, $82, $27, $22, $27, $22 $27, $22, $27, $22, $27, $22, $27, $29 $27, $22, $4A, $BB, $BA, $22, $92, $22 $92, $22, $92, $22, $92, $22, $92, $22 $92, $C2, $92, $44, $DB, $BB, $D2, $2C $22, $2C, $22, $2C, $22, $2C, $22, $2C $22, $2C, $22, $2C, $22, $2E, $FF, $FE end data ldat %00000111, %11111111, %11111111, %00000000 %00000000, %10000000, %00001000, %00000000 %00000000, %00000000, %00000000, %00000000 %00001001, %00000000, %00000100, %10000000 %00000010, %00000000, %00000010, %00000000 %00001111, %11111111, %11111111, %10000000 %00001010, %10101010, %10101010, %10000000 %00010000, %00000010, %00000000, %01000000 %00011111, %11111111, %11111111, %11000000 %00100000, %01000000, %00010000, %00100000 %00111111, %11111111, %11111111, %11100000 %00101010, %10101010, %10101010, %10100000 %01000000, %00000010, %00000000, %00010000 %01111111, %11111111, %11111111, %11110000 %11111111, %11111111, %11111111, %11111000 %10101010, %10101010, %10101010, %10101000 end for row = 0 to 175 if row & 1 then ldat_ptr = (L4p[L4p_ptr]&$0F)*4 else L4p_ptr = row/2 : ldat_ptr = (L4p[L4p_ptr] & $F0)/4 col = 0 byte_loop cbyte = ldat[ldat_ptr] : cbit = $80 bit_loop if cbyte & cbit then pfpixel col row on else pfpixel col row off col = col + 1 : cbit = cbit/2 if cbit then bit_loop ldat_ptr = ldat_ptr+1 if ldat_ptr & $03 then byte_loop next Edited September 9, 2013 by bogax Quote Link to comment Share on other sites More sharing options...
iesposta Posted September 9, 2013 Author Share Posted September 9, 2013 Thanks again. Your code is so elegant it is hard to follow even with your describing what is happening, however I can learn it, follow it, I know what it is doing after seeing it all coded. I think we may be able to drop the pfpixel off part because pfclear was added, so procedurally drawing a PF won't just add to what was there if pfclear is used. Now we just need a program that takes the raw PF and removes the duplicates, building the data table. I wish I knew java or python. Quote Link to comment Share on other sites More sharing options...
bogax Posted September 9, 2013 Share Posted September 9, 2013 (edited) I'm sick of fighing with the forum editorHere I've jiggered it to skip blank lines as wellas blank bytes.I rearranged the data a little bit JumpmanBLU20130902_4.bas Edited September 9, 2013 by bogax Quote Link to comment Share on other sites More sharing options...
SeaGtGruff Posted September 9, 2013 Share Posted September 9, 2013 Well, something falls apart: Plus that is the wrong center section. Edit: Also to fill the DPC+ data streams, I believe the PF1 and PF2 data would have to be together. Like PF1L & PF1R as one dataset. And I don't know if bB is drawing PF1 PF2 PF2 PF1 or PF1 PF2 PF1 PF2 ?? bB draws PF0-PF1-PF2-PF2-PF1-PF0, or a reflected playfield. It doesn't move anything into PF0L and PF0R, so the leftmost and rightmost 4 playfield pixels are blank (unless you move something into PF0). If the center section is wrong in that picture, it's because you're pushing the wrong column of data into that particular data queue (or whatever it's called in DPC+). For one thing, I can see by your code that you're pushing the data for the LEFT half of the screen. So what you need to do is figure out which queue goes with which data column. But now I realize there's a problem with MY code-- if you reverse the index like I did, then the nibbles also need to be handled in the reverse order: current_row = 87 decompress_L4_loop print_row = L4p[current_row] rem current_byte = print_row / 16 current_byte = print_row & 15 DF6PUSH = PF1L_L4_0n[current_byte] DF7PUSH = PF2L_L4_0n[current_byte] rem DF6PUSH = PF1R_L4_0n[current_byte] rem DF7PUSH = PF2R_L4_0n[current_byte] rem rem DF7PUSH = PF2R_L4_0n[current_byte] rem DF6PUSH = PF1R_L4_0n[current_byte] rem rem PF1L[2 * current_row] = PF1L_L4_0n[current_byte] rem PF2L[2 * current_row] = PF2L_L4_0n[current_byte] rem PF2R[2 * current_row] = PF2R_L4_0n[current_byte] rem PF1R[2 * current_row] = PF1R_L4_0n[current_byte] rem current_byte = print_row & 15 current_byte = print_row / 16 DF6PUSH = PF1L_L4_0n[current_byte] DF7PUSH = PF2L_L4_0n[current_byte] rem DF7PUSH = PF2R_L4_0n[current_byte] rem DF6PUSH = PF1R_L4_0n[current_byte] rem rem PF2L[2 * current_row + 1] = PF2L_L4_0n[current_byte] rem PF2R[2 * current_row + 1] = PF2R_L4_0n[current_byte] rem PF1R[2 * current_row + 1] = PF1R_L4_0n[current_byte] current_row = current_row - 1 if current_row <> 255 then goto decompress_L4_loop Sorry about that. Quote Link to comment Share on other sites More sharing options...
enthusi Posted September 9, 2013 Share Posted September 9, 2013 A bit Offtopic (forgive me), why do people use mirrored playfield for asymmetric gfx? Isnt it much more relaxed to set registers in normal repeat mode? Quote Link to comment Share on other sites More sharing options...
Thomas Jentzsch Posted September 9, 2013 Share Posted September 9, 2013 When your screen is pretty busy with other objects, I found it easier to handle when the PF is reflected. The invisible area is usually used for color and sprite changes. Some writes can be prepared at the very end of the previous scanline. So you should try to make your PF writes in the left to middle area of the visible scanline. And a reflected PF is better when you do not use or change PF0. Quote Link to comment Share on other sites More sharing options...
enthusi Posted September 9, 2013 Share Posted September 9, 2013 I can see that is helps to have most writes in one go with little need for delays inbetween, then again, even beginners often hassle with the reflected timing. Learning proper timing and THEN relax is probably better than the other way around, come to think of it Thanks. Quote Link to comment Share on other sites More sharing options...
Thomas Jentzsch Posted September 9, 2013 Share Posted September 9, 2013 Knowing the required earliest and latest update cycles (e.g. for the PF registers), I am always cycle counting my kernels instead of trial and error. For me that's definitely the easier way. Quote Link to comment Share on other sites More sharing options...
SeaGtGruff Posted September 9, 2013 Share Posted September 9, 2013 A bit Offtopic (forgive me), why do people use mirrored playfield for asymmetric gfx? Isnt it much more relaxed to set registers in normal repeat mode? I prefer to use the repeated mode for asymmetric playfields, due to the less stringent timing demands. As far as I know, the reflected mode offers no benefits with regard to timing, EXCEPT when you want to use a narrower playfield for whatever reason-- it might be to save some scan line cycles, or to save some data bytes (or both), or it might simply be for game design reasons. In that case you'd WANT to use the reflected mode so PF0 will be on the far left and far right sides of the screen and you can just initialize it to all 0s, or all 1s, or whatever, and then leave it be. Of course, you can draw a narrow playfield in the repeated mode, but then you have to update all three playfield registers twice each line, so (as I see it) the only good reason for doing it that way would be for game design reasons-- i.e., cycles per scan line aren't an issue, and ROM space isn't an issue, but nevertheless you want to use a narrow playfield for other reasons, and you decide to go with a repeated playfield because the relaxed timing for the playfield writes works out better for you. Actually, I think asymmetric playfields have sort of spoiled us. It's a little bit humbling to look at games like Tutankham and realize that they drew their "asymmetric" playfields by switching from the repeated mode to the reflected mode, and vice versa, from row to row. That requires more creativity-- and also saves you some cycles and data bytes. Quote Link to comment Share on other sites More sharing options...
Mr SQL Posted September 9, 2013 Share Posted September 9, 2013 Great thread! I see some parallels with the scrolling virtual worlds of the ASDK: The ASDK supports storing large virtual world bitmaps in Assembly data statements in WYSIWYG format just like bB defines playfields. The 20x10 playfield area can be panned from anywhere in the 92x20 virtual world using x,y camera coordinates and gets decompressed into a 40x10 pixel grid. Both of these operations happen either between screens, or via flicker in real time - an entire frame is used to handle the heavy calcs. SeaGtGruff made a good point about not being able to do complex operations like decompression during the scanlines; there's just about enough time to draw the display pixels and set the sprite objects so decompression and scrolling has to happen during a larger timeslice. I haven't used DPC+ but bB 1.0 stores it's 32x32 pixel playfields entirely in SuperChip RAM which allows the entire playfield to be malleable via pfpixel. The ASDK uses CBS RAM (256 bytes) to store it's 92x20 virtual worlds to the same effect - pixels are malleable using pfpixel commands. Since DPC+ has access to much more RAM and an ARM processor, supporting large virtual worlds of greater detail and a CAM object for scrolling the playfield could be possible future enhancements for the kernel Quote Link to comment Share on other sites More sharing options...
SeaGtGruff Posted September 9, 2013 Share Posted September 9, 2013 SeaGtGruff made a good point about not being able to do complex operations like decompression during the scanlines; there's just about enough time to draw the display pixels and set the sprite objects so decompression and scrolling has to happen during a larger timeslice. Actually, the point I originally made was that, depending on the "compression" method, it might be possible to do the "decompression" during the scan line loop. But then I realized that iesposta is using batari Basic and the DPC+ kernel, rather than assembly and a non-DPC+ kernel, so that rules out trying to do the "decompression" during the scan line loop. The method I proposed-- storing the data for just the unique row patterns, then having an index to point to whichever pattern needs to be drawn on a given row-- is not really "compression" per se in my opinion, but is really just an additional level of "indirection." So as long as you have a few extra cycles free in your scan line kernel, you could do the following: kernel_loop LAX (PF_rows),Y ; assumption is that Y is the index for the current scan line LDA PF0L_data,X STA PF0 LDA PF1L_data,X STA PF1 LDA PF2L_data,X STA PF2 LDA PF2R_data,X STA PF2 LDA PF1R_data,X STA PF1 LDA PF0R_data,X STA PF0 DEY BNE kernel_loop Obviously that isn't a complete kernel-- you'd want to do some of those commands at just the right cycle, you might not want to update PF0, and you may or may not want to do a WSYNC in there somewhere, not to mention drawing the sprites-- but there's only one extra line in there, the LAX (PF_rows),Y instruction to get the desired PF pattern for the current scan line. (The assumption is that PF_rows_lo and PF_rows_hi have been set to point to the appropriate table of pattern IDs for the current level.) Then you just read from the PF data tables using X as the index instead of Y. This example assumes that Y is being used with the (Indirect),Y addressing mode for drawing the sprites, so it uses X as the index into the actual PF data, which means you'd either need a separate kernel loop for every screen (each containing different table addresses for the LDA PF_data,X instructions), or put the kernel loop in RAM and execute it in RAM so you can modify the PF_data table addresses on the fly, or have no more than 256 unique row patterns for all screens such that you need only one set of PF_data tables rather than separate ones for each level screen. Note also, this example assumes that the row pattern numbers are stored one-per-byte, rather than squeezing two into a byte (high nibble, low nibble). Quote Link to comment Share on other sites More sharing options...
iesposta Posted September 10, 2013 Author Share Posted September 10, 2013 bogax, To let you know, that saved over 480 bytes. In my game, that screen would be the best-case scenario. The rest are more complex. The pie/cement factory has 19 unique PF lines. So I guess using nybbles are out. The start Level 1 has around 76 unique PF lines. JumpmanBLU20130909a.bas JumpmanBLU20130909a.bas.bin Quote Link to comment Share on other sites More sharing options...
iesposta Posted September 10, 2013 Author Share Posted September 10, 2013 SeaGtGruff, Your routine has got me this far. The entire right half of the PF is perfect. (One bad index bit.) I also had to flip horizontally each byte?? in PF1R_L4_0n DF6LOW = 184 DF7LOW = 184 DF6HI = 7 DF7HI = 6 Puts data into the left half of the screen, but it fills all weird. JumpmanBLU20130908.bas JumpmanBLU20130908.bas.bin Quote Link to comment Share on other sites More sharing options...
Thomas Jentzsch Posted September 10, 2013 Share Posted September 10, 2013 I think one should read DF7HI. 1 Quote Link to comment Share on other sites More sharing options...
bogax Posted September 10, 2013 Share Posted September 10, 2013 bogax, To let you know, that saved over 480 bytes. In my game, that screen would be the best-case scenario. The rest are more complex. The pie/cement factory has 19 unique PF lines. So I guess using nybbles are out. The start Level 1 has around 76 unique PF lines. rig it so you can change dictionaries I didn't actual tote it up but I would guesstimate that if a line is repeated only once ie it occurs twice you only break even. the ladders are where you realy save. changing dictionaries would add some overhead but I don' think it would matter how many unique lines there are so much as how much redundancy there is ie how many times each line is repeated you haven't shown your other screens you could try and adopt a playfield design rule that any particular unique line will be used at least twice. Quote Link to comment Share on other sites More sharing options...
SeaGtGruff Posted September 10, 2013 Share Posted September 10, 2013 SeaGtGruff, Your routine has got me this far. The entire right half of the PF is perfect. (One bad index bit.) I also had to flip horizontally each byte?? in PF1R_L4_0n DF6LOW = 184 DF7LOW = 184 DF6HI = 7 DF7HI = 6 Puts data into the left half of the screen, but it fills all weird. I don't know if bB's DPC+ kernel reverses the playfield bytes automatically. If you're making an assembly game then you definitely need to reverse the bytes yourself. If you're using the "playfield:" statement in bB then the bB compiler will automatically reverse the bytes where appropriate. But if you used vbB's playfield tool to draw the playfield and are then pushing the data into the RAM queues, I don't know how vbB and bB and DPC+ handle the bytes. Thanks for posting the .BAS file; I'll take a look at it, try compiling it, check the assembly listing, etc., to see if I can figure out what's what. Quote Link to comment Share on other sites More sharing options...
iesposta Posted September 10, 2013 Author Share Posted September 10, 2013 I don't know if bB's DPC+ kernel reverses the playfield bytes automatically. If you're making an assembly game then you definitely need to reverse the bytes yourself. If you're using the "playfield:" statement in bB then the bB compiler will automatically reverse the bytes where appropriate. But if you used vbB's playfield tool to draw the playfield and are then pushing the data into the RAM queues, I don't know how vbB and bB and DPC+ handle the bytes. Thanks for posting the .BAS file; I'll take a look at it, try compiling it, check the assembly listing, etc., to see if I can figure out what's what. I got it working today. I had to dupicate your loop, since I really can't follow what's going on. This is important batari Basic research, I feel, because changing a playfield, or most likely part of a playfield, during a game gives many possibilities. It would be nice to have a routine that can set short rows of Playfield. Probably a small section scrolling like in Parker Brothers Star Wars - The Emperor Strikes Back. JumpmanBLU20130910PUSHa.bas JumpmanBLU20130910PUSHa.bas.bin Quote Link to comment Share on other sites More sharing options...
SeaGtGruff Posted September 11, 2013 Share Posted September 11, 2013 (edited) I got it working today. I had to dupicate your loop, since I really can't follow what's going on. This is important batari Basic research, I feel, because changing a playfield, or most likely part of a playfield, during a game gives many possibilities. It would be nice to have a routine that can set short rows of Playfield. Probably a small section scrolling like in Parker Brothers Star Wars - The Emperor Strikes Back. That's good news! As far as what's going on in the loop I posted, it retrieves the pattern ID (or the index into the unique rows tables) for each row. But in this case there are 2 IDs per byte (1 per nibble), so the loop has to be a "2-line loop" so 1 pass through the loop can process 2 nibbles (1 byte). Rather than use a "for...next.. step 2" (or "step -2") loop, I decided to set the starting value for the counter and then increment or decrement it manually. The IDs are stored in the nibbles with the 1st row in the high nibble and the 2nd row in the low nibble. So if you're incrementing the counter (moving forward through the list) you need to process the high nibble first, then the low nibble. So for the 1st row you use byte/16 to get the value of the high nibble, then use that value to retrieve the data for the 1st row from the tables. For the 2nd row you use byte&15 to mask off the high nibble and get the value of just the low nibble, then use that value to retrieve the data for the 2nd row from the tables. But that displays the playfield upside-down, so we decrement the counter instead (start at the bottom and go up). That means we need to do the low nibble first, followed by the high nibble (my boo-boo was I forgot to do that). So for the 1st row you use byte&15, then for the 2nd row you use byte/16. If you have more than 16 unique patterns and can't squeeze 2 IDs (or pointers) into 1 byte, then you can use a 1-line loop and dispense with the byte&15, byte/16 stuff. By the way, I used this method of "compression" (or "indirection") to revisit a "playfield text" titlescreen that I'd done years ago for a game idea. I'd previously "compressed" the data by creating a data table as follows: number of times to loop to process the whole list number of times the 1st data byte occurs actual value for the 1st data byte number of times the 2nd data byte occurs actual value for the 2nd data byte etc. For example, suppose you have the following list of numbers (data bytes): 4 8 8 8 4 4 4 4 4 5 2 2 8 8 8 8 I compressed the list as follows: 1, 4 (4 occurs 1 time) 3, 8 (8 occurs 3 times) 5, 4 (4 occurs 5 times) 1, 5 (etc.) 2, 2 4, 8 Then I added another number at the beginning of the list to tell me how many times I needed to loop: 6 (there are 6 pairs of numbers to be processed) 1, 4 (1st pair) 3, 8 (2nd pair) 5, 4 (3rd pair) 1, 5 (etc.) 2, 2 4, 8 What I was doing was decompressing the compressed playfield data, storing it in RAM (using MNetwork bankswitching with extra RAM), and then reading/displaying the decompressed playfield from RAM. By rewriting that to use the "indirection" method, I'm now able to display the "compressed" playfield directly from ROM by doing the "decompression" inside the scanline loop: LDX #166 ; 02 ; 05 Active_Lines LDY Title_Rows-1,X ; 04+ ; 10 LDA Title_PF0LR,Y ; 04+ ; 14 STA PF0 ; 03 ; 17 LDA Title_PF1L,Y ; 04+ ; 21 STA PF1 ; 03 ; 24 LDA Title_PF2L,Y ; 04+ ; 28 STA PF2 ; 03 ; 31 LDA Title_PF0LR,Y ; 04+ ; 35 ASL ; 02 ; 37 ASL ; 02 ; 39 ASL ; 02 ; 41 ASL ; 02 ; 43 STA PF0 ; 03 ; 46 LDA Title_PF1R,Y ; 04+ ; 50 STA PF1 ; 03 ; 53 LDA Title_PF2R,Y ; 04+ ; 57 STA PF2 ; 03 ; 60 STA WSYNC ; 03 ; 00 DEX ; 02 ; 02 BNE Active_Lines ; 02++ ; 04 In this case, I'm using all 40 playfield pixels, and storing the PF0L and PF0R nibbles in 1 byte, hence the need to use ASL to shift the data for the right copy of PF0. And this method was feasible only because I'm not doing anything else in the loop, just drawing the playfield to display the title text. I haven't checked the number of bytes used by the old method versus the new method, but I think the new method takes fewer bytes to store the data-- since any values that occur in different places (like 4 and 8 in my example) are stored just once-- and the "decompression" process is faster and takes less code, since I don't need to decompress the data into RAM ahead of time and then display it from RAM, everything is done as the display is being drawn. Edited September 11, 2013 by SeaGtGruff Quote Link to comment Share on other sites More sharing options...
bogax Posted September 11, 2013 Share Posted September 11, 2013 (edited) here I added a reverse functionof course it uses 50 bytes or soand it's slowerJumpmanPUSHa_3.bas Edited September 11, 2013 by bogax Quote Link to comment Share on other sites More sharing options...
bogax Posted September 12, 2013 Share Posted September 12, 2013 (edited) Here I rearranged SeaGtGruff's code andadded the facility to switch dictionaries(sets of lines)Instead of referencing the lines relativeto the start of the table(s) they are relativeto dbase.When a zero is encountered the next byteis loaded to dbase this adds two bytes tothe count (crow)ie a count of 88 for the lines+ 2 for each time dbase is set.So here crow is intialized to 90 (one countgets thrown away at the start of the loop0-87 inclusive for the lines + 2 for settingdbase)The switches will have to be byte aligned, a switchmust happen on an even number of lines (2 linesper byte)JumpmanBLU.bas Edited September 12, 2013 by bogax 1 Quote Link to comment Share on other sites More sharing options...
iesposta Posted September 12, 2013 Author Share Posted September 12, 2013 Now we need a "Playfield Tool" that takes an entire playfield and outputs data and tables. Isn't that what computers are good at? At this point I still have to type in data tables. Also, "When a zero is encountered" - is that a zero bit or zero byte? I guess I can study the code. Quote Link to comment Share on other sites More sharing options...
enthusi Posted September 12, 2013 Share Posted September 12, 2013 (edited) I wrote nonsense Edited September 12, 2013 by enthusi Quote Link to comment Share on other sites More sharing options...
SeaGtGruff Posted September 12, 2013 Share Posted September 12, 2013 "When a zero is encountered" - is that a zero bit or zero byte? From the context, it must mean byte, since it skips to the next byte. I'm not sure how skipping bits or bytes because they're 0 would work out. Wouldn't this leave the existing data unaltered? What I mean is, DPC+ doesn't automatically initialize the playfield bytes to 0 on every line, does it? So if you're setting the bits individually and skip a bit because it's 0, or if you skip an entire byte because it's 0, wouldn't that just leave the old data in the playfield bit/byte? Quote Link to comment Share on other sites More sharing options...
bogax Posted September 12, 2013 Share Posted September 12, 2013 From the context, it must mean byte, since it skips to the next byte. I'm not sure how skipping bits or bytes because they're 0 would work out. Wouldn't this leave the existing data unaltered? What I mean is, DPC+ doesn't automatically initialize the playfield bytes to 0 on every line, does it? So if you're setting the bits individually and skip a bit because it's 0, or if you skip an entire byte because it's 0, wouldn't that just leave the old data in the playfield bit/byte? Just to clarify how I'm using the teminology you have a list of "symbols" which are nibbles which are used as pointers into a "dictionary" of possible lines to be pushed to the playfield The lookup into the dictionary is now relative to an offset called dbase so instead of a symbol/nibble being relative to the beginning of the dictionary it's relative to dbase the look up is (dbase + symbol) relative to the beginning of the dictionary. Instead of 16 possible line symbols there's now 15 because zero is used for switching dbase If a zero byte is encontered in the symbol list dbase gets set to the byte after the zero. I't doesn't miss pushing any of the lines, it skips two bytes in the symbol/nibble list which is why the count (and the symbol list) must be increased by two, one for the zero marker and one for the new offset into the symbol list. A blank line is still in the dictionary I was considering making a blank line a special case and not putting it in the dictionary since it's likely to be used any where. I know almost nothing about DPC+ but I assume that to get the data fetchers to skip a line would require setting them to new values which seems like a lot more bother than it would be worth just for blank lines. But is might be worth while if you could change just a part of the playfield. Quote Link to comment Share on other sites More sharing options...
+Random Terrain Posted September 12, 2013 Share Posted September 12, 2013 So is there something everyone agrees is the best way yet or are you still working on it? Is it time to show jwierer to see if he can add it to VbB? Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.