Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sprite Color Replacement #14

Open
ghost opened this issue Nov 23, 2015 · 20 comments · Fixed by #69
Open

Sprite Color Replacement #14

ghost opened this issue Nov 23, 2015 · 20 comments · Fixed by #69
Assignees
Milestone

Comments

@ghost
Copy link

ghost commented Nov 23, 2015

It would be very useful to have a function to draw sprites beeing able to change colors on them.

For example for beeing able to have differente enemy sprites that only differ on color, so we only need one sprite in memory.

@AugustoRuiz
Copy link
Collaborator

If we use the table based approach to draw sprites (where the output of the pixel is pre-calculated), we could have several tables, so that colors can be changed... It would cost 256 bytes per color-schema, so depending on the number of sprites, it might be too much.

Other way to do so might be to restrict the sprite to a given number of colors, so that the look up table becomes significantly smaller.

@ghost
Copy link
Author

ghost commented Nov 23, 2015

What I would do would be define swap color tables.

For example if I want to change 3 colors of a sprite, pass an array of 6 chars indicating each color and its replacement to the function to draw sprites.

I don't know it this may lead to a slow implementation, but from the user point of view (me :) ) that would be very easy and intuitive to use.

@lronaldo
Copy link
Owner

I think your feature request is clear. In fact, something similar was already on the TODO list for CPCtelera. AugustoRuiz is pointing to possible implementations, as replacing pixels of a sprite while drawing is very very slow unless some programming tricks are used (just look at the difference betweeen drawing ROM characters and drawing sprites. It's really huge).

In fact, this is a feature quite difficult to generalize. I think we should think of several cost/effective approaches and let the user select the most appropriate for each concrete project. We will require some time to think of it and come with some prototypes for testing.

@lronaldo lronaldo added this to the 1.5 milestone Nov 23, 2015
@ghost
Copy link
Author

ghost commented Nov 23, 2015

I leave the implementation to experts. If I knew how to implement this efficiently I would have created a pull request, not an issue. :)

Thank you.

@lronaldo lronaldo modified the milestones: 2.0, 1.5 Apr 22, 2016
@lronaldo lronaldo self-assigned this Jan 16, 2018
@lronaldo
Copy link
Owner

Created new branch named colorreplace for this feature

@lronaldo
Copy link
Owner

This issue is currently under development in the colorreplace branch. @Arnaud6128 initiated development with a pull request that has already been merged to the branch. Now functions are being reviewed and improved. You may check development done up to now at pull back #69

@Arnaud6128
Copy link
Collaborator

Hi,
what do you think about merging these two branches ?
The color replace functions are working well and could be integrated in development without needing to switch branch.

@lronaldo
Copy link
Owner

Hi @Arnaud6128,

Last time I look at your routines they were working well, and I stopped in the middle of reviewing them along with their documentation.

I think your routines are great and they should be added to development branch. Maybe, it would be more interesting if all contributors have a chance to look at them and see if they can improve them, fix any typo or things like that.

I will have a diagonal look again at all of them and proceed to merge with the development branch for other contributors to have the oportunity to look at them :)

Thanks for all your contributions, @Arnaud6128, and please, excuse me for taking so long to answer and review your latest contributions.

Cheers!

@lronaldo
Copy link
Owner

lronaldo commented Aug 27, 2021

Hi @Arnaud6128 and others:

After some thoughts I've finally found a way to perform the pixel-colour-replacement operation using only bit operations, effectively parallelizing it 4-by-4-pixels in Mode 1. Last time I tried it I was unable to find a way, but I somehow knew it should be possible. I have not tried this tecnique on Mode 0 pixels yet, but I assume it can be easily translated.

This can make colour replacement much much faster and simpler, which will make it more usable in actual games. At the end of this post you can finde a little concept demo you can analyze to better get the idea. Just paste it into a freshly new ASM CPCtelera project replacing main.s.

I think it would be really nice to convert all functions to this technique to get better performance and less memory footprint.

Hope you enjoy this idea and we can have great Colourizing functions in CPCtelera soon :).

@Arnaud6128, thanks very much for all your work. You founded the basis for this functionality and deserve much of the credit. It was your original code which suggested me this possiblity. Thanks mate :)

;;-----------------------------LICENSE NOTICE------------------------------------
;;  This file is part of CPCtelera: An Amstrad CPC Game Engine 
;;  Copyright (C) 2018 ronaldo / Fremos / Cheesetea / ByteRealms (@FranGallegoBR)
;;
;;  This program is free software: you can redistribute it and/or modify
;;  it under the terms of the GNU Lesser General Public License as published by
;;  the Free Software Foundation, either version 3 of the License, or
;;  (at your option) any later version.
;;
;;  This program is distributed in the hope that it will be useful,
;;  but WITHOUT ANY WARRANTY; without even the implied warranty of
;;  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;;  GNU Lesser General Public License for more details.
;;
;;  You should have received a copy of the GNU Lesser General Public License
;;  along with this program.  If not, see <http://www.gnu.org/licenses/>.
;;-------------------------------------------------------------------------------

;; Include all CPCtelera constant definitions, macros and variables
.include "cpctelera.h.s"

.area _DATA
.area _CODE

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;  MAIN
;; Tests the new functionality of replacing the colours in Mode 1
;; 4-by-4 pixels at a time using only bit operations to make it parallel
;;
_main::
   ld    hl, #0xC000    ;; Where to draw in video memory
   ld    bc, #0         ;; Colour to replace (Find    Colour)
   ld     a, #1         ;; New Colour        (Insert  Colour)
   call  draw256combinations_in_a_line ;; Do replacement and draw
   jr     .
 

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;  Draws a 256 byte long line starting at HL. At each byte of the line
;; it uses L as byte pattern to draw 4 pixels (Mode 1). This ensures 
;; that all possible 4-pixel byte patterns are tested. 
;;  For each 4-pixels drawn, it runs replace_pixel_by_colour to get
;; a new byte pattern where pixels of BC Pen Colour have been replaced
;; by A New Colour.
;;  Then it draws the new byte-pattern after replacement immediately 
;; below the previous pattern (for comparison purposes)
;;
;; Inputs
;;    HL -> Pointer to video memory to draw the line 
;;          **(Pixel 0-5 line of any character and 00 aligned!, L must be 00)
;;    BC -> Colour PEN to replace (0-3) (Find   Colour)
;;    A  -> New Colour PEN  (0-3)       (Insert Colour)
;;
draw256combinations_in_a_line:
   ld (save_a), a          ;; Save Insert Colour for later usage
   ld (save_bc), bc        ;; Save New Colour for later usage
loop_pl:
   ;; Set the next 4-pixels to be converted
   ;; And draw it to video memory
   ld    de, #sprite_bytes ;; DE = Pointer to sprite bytes
   ld     a, l             ;; A = New 4-pixel byte-pattern (LSB of HL)
   ld    (de), a           ;; *DE = 4-pixel byte-patterh (Save pattern into Sprite Bytes)
   ld    (hl), a           ;; *HL = 4-pixel byte-patterh (Draw pattern to screen at HL)
   
   ;; Convert the next 4-pixels byte-pattern
  save_a = .+1
   ld     a, #0            ;; A = Insert Colour (placeholder)
  save_bc = .+1
   ld    bc, #0            ;; BC = Find Colour (placeholder)
   push  hl                ;; Save HL
   call  replace_pixel_by_colour ;; Convert the 4-pixels byte-patterh
   pop   hl                ;; Restore HL
   ;; Draw the converted 4-pixels byte-pattern
   ;; with BC substituted by A
   ld    bc, #0x800        ;; / HL += 0x800
   add   hl, bc            ;; \  Point to next screen line
   ld    (hl), a           ;; *HL = A  Draw converted 4-pixels byte-pattern
   sbc   hl, bc            ;; HL -= 0x800, Point back to original line
   ;; Increment Screen Video Memory Pointer and check for ending
   inc   hl                ;; ++HL  Point to next byte to the right (next 4-pixels)
   ld     a, l             ;; /
   or     a                ;; | If (L != 00) Continue
   jr    nz, loop_pl       ;; \   End when we have cycled (L==00 again)
   ret                     ;; Finished, return

;; Simulates a sprite array, with just 1 element (4 Mode 1 pixels)
sprite_bytes:  .db #0b01010011     

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;  Replaces pixels of 'BC' PEN Colours by pixels of 'A' PEN Colour 
;; insed a 4-pixels byte-pattern. It reads the 4-pixels byte-pattern
;; from DE (simulating it is the array of an sprite) and
;; replaces BC -> A pixels in it. 
;;
;; Input:
;;    DE -> Pointer to sprite_bytes (actually 1 4-pixels byte-pattern)
;;    BC -> PEN Colour to be replaced (0-3) (Find   Colour)
;;     A -> New Pen Colour (0-3)            (Insert Colour)
;;
rplc_table:: .db 0x00, 0xF0, 0x0F, 0xFF
replace_pixel_by_colour:
   ;; Transform pens into 4-pixel byte-patterns
   ;; First, Find Colour -> Find Pattern
   ld    hl, #rplc_table   ;; [3] HL = Pointer to Mode 1 4-pixels byte-pattern table 
   add   hl, bc            ;; [3] HL += BC // Add offset in the table. HL Points to byte-pattern for
                           ;;             // Find Colour (Find Pattern)
   ld     c, a             ;; [1]  C = New Colour (Save for later use)
   ld     a, (hl)          ;; [2]  A = Find Pattern (Pattern with the 4-mode-1-pixels in Find Colour PEN)
   ;; Second, Insert Colour -> Insert Pattern  
   ld    hl, #rplc_table   ;; [3] HL = Pointer to Mode 1 4-pixels byte-pattern table 
   add   hl, bc            ;; [3] HL += BC // Add offset in the table. HL Points to byte-pattern for
                           ;;             // Insert Colour (Insert Pattern)
   ld     c, (hl)          ;; [2]  C = Insert Pattern (Pattern with the 4-mode-1-pixels in Insert Colour PEN)
   ld     b, a             ;; [1]  B = Find Pattern (FindPat)
   xor    c                ;; [1]  / C = InsrPat ^ FindPat
   ld     c, a             ;; [1]  |  At the end we will insert C using XOR with something that will contain 
                           ;;      \  FindPat cancelling it (2 XORs), but InsrPat will remain (only this one XOR)
   
   ;; Get byte and transform it
   ld     a, (de)    ;; [2] / D = Get the Original byte (OrigByte) with 4-pixels 
   ld     d, a       ;; [1] \     where to perform the replacement
   xor    b          ;; [1] / A = E = (OrigByte ^ FindPat)   // This is to find which pixels to replace
   ld     e, a       ;; [1] \        // Pixels Found to be replaced = 00, Other Pixels != 00
   rrca              ;; [1] / A' = ROR( OrigByte ^ Find, 4)
   rrca              ;; [1] |    // E  viewed as 4-pixels: [ a b c d A B C D ] : Pixels [Aa] [Bc] [Cc] [Dd]
   rrca              ;; [1] |    // A' viewed as 4-pixels: [ A B C D a b c d ] 
   rrca              ;; [1] \    // The 2 bits of each pixel are aligned 
   or     e          ;; [1] / A'' = (OrigByte ^ Find) | ROR(OrigByte^Find, 4)
                     ;;     |     // A'' = MASK. As both bits of Pixels Found to be replaced are 0, ORing them
                     ;;     \     // gives 0. Similarly, as at least 1 bit of other pixels is 1, Oring them gives 1
   cpl               ;; [1] A'''  = ~MASK // We negate the MASK to get FoundPixels=11, OtherPixels = 00
   and    c          ;; [1] A'''' = ~MASK & (InsrPat ^ FindPat) // We Mask our original insert pattern, 
                     ;;          // To leave ones only in the bits of the pixels that shall be replaced
                     ;;          // Result: Insert Pattern => FoundPixels=untouched, OtherPixels=00 
   xor    d          ;; [1] A''''' = OrigByte ^ (~MASK & (InsrPat ^ FindPat))
                     ;;          // We modify only the bits of the pixels of the Original Byte of Found Pixels
                     ;;          // thanks to the ~MASK that left only those bits out of (InsrPat ^ FindPat).
                     ;;          // So the Original Byte is modified by XORing it with (InsrPat ^ FindPat)
                     ;;          // only in the FoundBits (others are 00, so they are left equal after XOR)
                     ;;          // The XOR operation on each pixel is PixBits ^ (InsrPat ^ FindPat), which is
                     ;;          // the same as (PixBits ^ FindBits) ^ InsrPat, which is the same as
                     ;;          // (00) ^ InsrPat, as PixBits and FindBits are equal. This lefts New Colour inserted

   ret               ;; [3] Return

@lronaldo
Copy link
Owner

I have performed some tests replacing the code in cpct_spriteColourizeM1.asm with this proposal and these are the resulting measures:

;; ------------------------------------------------------
;; Sprite Size |  Previous   | Proposed |   Gain (%)    |
;; ------------------------------------------------------
;;  W=2,H=16   | 1635 / 1731 |    715   | 56.2% / 58.7% |
;;  W=4,H=32   | 6323 / 6707 |   2827   | 55.3% / 57.9% |
;; ------------------------------------------------------

I have also created 2 new functions cpct_pen2pxPatternM1_asm and cpct_2pens2pxPatternsM1_asm in order for the user to be able to precalculate colour patterns. If you wished to replace PEN 1 by PEN 3 in 10 sprites, you only create 1 pair of pixel colour patterns and use it as parameter for the 10 calls, calculating it 1 time instead of 10. Moreover, this patterns do not need to be calculated if they are constant (hence, I will also be adding two macros) and you could also do some interesting effects with the patterns. You may, if you wished, substitute only PEN 1 by PEN 3 in the even pixels, or in each one out of four... That might be useful to perform effects, like creating stripped T-shirts out of a sprite with a plain one, or a kind of temporal-immunity effect by rendering the sprite with strips that move along at each frame.

I am now cleaning up the changes and I will upload them shortly. I will need help to propagate this changes to the other Mode 1 functions. Who is available for help?

@Arnaud6128
Copy link
Collaborator

Hi, i trying to adapt for mode 0, what i have done :

  • In Find and Insert Colour replace dc_mode1_ct by dc_mode0_ct
  • In pixels replace, make only two rrca

Is something else to modify ?

color_m0_test

@lronaldo
Copy link
Owner

Hi, @Arnaud6128

Doing only 2 RRCAs is not enough for the algorithm to perform the same operation. That part of the algoritms what does is to mix all the bits of a single pixel into 0 (if all of them are 0) or 1 (if at least one of them is one) to create a mask. Therefore, as mode 0 has 4 bits, it is not enough mixing 2 pairs of 2 bits. I haven't analyzed it properly, but the first valid sequence I can thing of is this one:

   ;;; Original Byte in A = E = [ A 1 B 2 C 3 D 4 ] : Pixels [ABCD] [1234]
   rrca        ;; [1] / E   = [  A  1  B  2  C  3  D  4 ]
   rrca        ;; [1] | A   = [  D  4  A  1  B  2  C  3 ]
   or     e    ;; [1] \ A|E = [ AD 14 AB 12 BC 23 CD 34 ]
   ld     e, a ;; [1] E = A
   rrca        ;; [1] /   
   rrca        ;; [1] |  
   rrca        ;; [1] | E   = [   AD   14   AB   12   BC   23   CD   34 ]
   rrca        ;; [1] | A   = [   BC   23   CD   34   AD   14   AB   12 ]
   or     e    ;; [1] \ A|E = [ ABCD 1234 ABCD 1234 ABCD 1234 ABCD 1234 ]

It probably might be improved by rethinking it. Additionally, dt_mode0_ct might not be appropriate as conversion table. Need to check it previously.

I'll try to upload the first complete version of spriteColourizeM1 ASAP so that we can extend it to the other M1 functions. I will probably think of merging the branch with development and we can then continue there.

It might be a good time to rethink CPCtelera branches to simplify contribution workflow. I think it is too slow if contributions are big and have to wait for me to review. It might be a good time to decide on some foundational principles, and roadmap to produce a more open contribution model :)

Thank you very much, @Arnaud6128 :)

Cheers!

@Arnaud6128
Copy link
Collaborator

Hi, @lronaldo
It's better but not totally working.

ballon_test2

I will wait for your function spriteColourizeM1 to use it to the other M1 functions. I will do the same for M0 functions, i am losing time to debug potentialy obsolete M0 functions.

And yes a roadmap will be really useful to know what will be done and when (and have a version release date ?)

@lronaldo
Copy link
Owner

Hi all again (@Arnaud6128 @AugustoRuiz @AmstradGamer),

colorreplace branch is now merged with development and so the colorReplace functions are available in CPCtelera 1.5 WIP. Thanks @Arnaud6128 for doing the main work and thanks all others for proposing, collaborating and participating :)

Right now, only the functions cpct_spriteColourizeM1 and cpct_spriteMaskedColourizeM1 contain the most recent code, with the latest optimization proposed 4 days ago. However, both of them have been fully developed, commented, tested and documented. Both of them are production-ready, as far as i can tell.

Now, we need to propagate these changes to the other M1 Colourize functions and fix the M0 version.

With respect to the M0 version that @Arnaud6128 has been testing, the only problem that remains in the last example is that the dc_mode0_ct table is not valid for the purposes of this function. That table holds bytes representing 1 pixel coloured and the other zeroed, but required values should have both pixels coloured with same colour. That's the reason for the resulting sprites being stripped: only 1 out of each 2 pixels is changed. That's easy to solve :)

Therefore, I propose continuing developing this functions againts development branch to leave all of them production ready. I suggest doing individual Pull Requests for each function: that will be easier to review and merge. Of course, I mean a PR per function, not per file. Changing one function usually affects several files :).

After solving current PRs we can then define a roadmap to finally publish 1.5 as stable version. Then we will be able to proceed setting up the new organization for CPCtelera to continue evolving :).

Thanks you all :)

@ToniRamirezM
Copy link

Thanks for doing this and sorry for not being able to help right now.

@lronaldo
Copy link
Owner

No worries, @ToniRamirezM :). I'm the first one who has been out for months. Whenever you can, be it tomorrow or next year, it would be nice if you can test the functions. No hurries at all, just enjoy ;).

@Arnaud6128
Copy link
Collaborator

Arnaud6128 commented Sep 1, 2021

Working on convertion of M1 to M0 functions and seems to be ok.

ballons_ok

I will continue with propagate changes to the other M0 Colourize functions.

Edit : In cpct_spriteMaskedColourizeM1_cbindings.s the save of IX and its restoration at end are missing.

@Arnaud6128
Copy link
Collaborator

All functions colourize converted and example updated to test all functions. Waiting for review on M0 functions to work on cpct_drawSpriteColorizeXXXM1.

@Arnaud6128
Copy link
Collaborator

Arnaud6128 commented Sep 18, 2021

All draw sprite colourize for M1 are coded / tested and here a small example to test it.
testM1Colors.zip

Now all functions for colourize sprites M0 and M1 are coded and are waiting to be merged.
Good reviewing 😄

@ghost
Copy link
Author

ghost commented Sep 18, 2021

Tested M0 functions and they work as expected.

Good work! @Arnaud6128

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
4 participants