diff --git a/src/crab/common/util.cr b/src/crab/common/util.cr index 4602951..ac1636e 100644 --- a/src/crab/common/util.cr +++ b/src/crab/common/util.cr @@ -81,3 +81,9 @@ macro log(value, newline = true) {% end %} {% end %} end + +struct Slice(T) + def [](index : Int, t : R.class, index_r : Int) : R forall R + Pointer(R).new((self.to_unsafe + index).address)[index_r] + end +end diff --git a/src/crab/gba/ppu.cr b/src/crab/gba/ppu.cr index c146551..30c6515 100644 --- a/src/crab/gba/ppu.cr +++ b/src/crab/gba/ppu.cr @@ -325,15 +325,83 @@ module GBA end def calculate_color(col : Int) : UInt16 - enables, effects = get_enables(col) + enable_flags, effects_enabled = get_enables(col) top_color = nil + colors = uninitialized Color[5] + sprite_pixel = @sprite_pixels[col] + colors[0] = Color.new( + sprite_pixel.priority, + sprite_pixel.palette, + sprite_pixel.blends, + 4, # sprites are bit 4 in the enable flags + true, # sprite + ) + 4.times do |bg| + colors[bg + 1] = Color.new( + @bgcnt[bg].priority, + @layer_palettes[bg][col], + true, # backgrounds always blend + bg, + false, # not a sprite + ) + end 4.times do |priority| - if bit?(enables, 4) + colors.each do |color| + if bit?(enable_flags, color.target_bit) # check that layer is enabled + if color.priority == priority # check that priority matches (effectively orders backgrounds) + if top_color.nil? + if color.palette > 0 # check that color is opaque + if !effects_enabled || !color.blends # if effects are disabled, always take the first opaque color + return BGR16.new(@pram, color).value + elsif @bldcnt.is_bg_target(color.target_bit, target: 1) + top_color = color + end + end + else # top color has been selected + top_color_u16 = BGR16.new(@pram, top_color).value + if !@bldcnt.is_bg_target(color.target_bit, target: 2) # second layer isn't set in bldcnt, don't blend + return top_color_u16 + elsif color.palette > 0 # is a target and color is opaque + if @bldcnt.blending_mode == 1 || top_color.sprite + return (BGR16.new(@pram, top_color) * (Math.min(16, @bldalpha.eva_coefficient) / 16) + + BGR16.new(@pram, color) * (Math.min(16, @bldalpha.evb_coefficient) / 16)).value + elsif @bldcnt.blending_mode == 0 + return top_color_u16 + elsif @bldcnt.blending_mode == 2 + # blend with white + return top_color_u16 # todo + elsif @bldcnt.blending_mode == 3 + # blend with black + return top_color_u16 # todo + end + else # is a target and color is transparent + if @bldcnt.blending_mode == 0 + return top_color_u16 + elsif @bldcnt.blending_mode == 1 + return top_color_u16 + elsif @bldcnt.blending_mode == 2 + # blend with white + return top_color_u16 # todo + elsif @bldcnt.blending_mode == 3 + # blend with black + return top_color_u16 # todo + end + end + end + end + end + end + end + backdrop_color = @pram.to_unsafe.as(UInt16*)[0] + return top_color.nil? ? backdrop_color : BGR16.new(@pram, top_color).value + + 4.times do |priority| + if bit?(enable_flags, 4) sprite_pixel = @sprite_pixels[col] if sprite_pixel.priority == priority && sprite_pixel.palette > 0 selected_color = (@pram + 0x200).to_unsafe.as(UInt16*)[sprite_pixel.palette] if top_color.nil? # todo: brightness for sprites - if !(sprite_pixel.blends || (@bldcnt.is_bg_target(4, target: 1) && effects)) + if !(sprite_pixel.blends || (@bldcnt.is_bg_target(4, target: 1) && effects_enabled)) return selected_color elsif @bldcnt.color_special_effect == 1 # alpha blending top_color = selected_color @@ -355,13 +423,13 @@ module GBA end end 4.times do |bg| - if bit?(enables, bg) + if bit?(enable_flags, bg) if @bgcnt[bg].priority == priority palette = @layer_palettes[bg][col] next if palette == 0 selected_color = @pram.to_unsafe.as(UInt16*)[palette] if top_color.nil? - if @bldcnt.color_special_effect == 0 || !@bldcnt.is_bg_target(bg, target: 1) || !effects + if @bldcnt.color_special_effect == 0 || !@bldcnt.is_bg_target(bg, target: 1) || !effects_enabled return selected_color elsif @bldcnt.color_special_effect == 1 # alpha blending top_color = selected_color @@ -427,6 +495,8 @@ module GBA when 0x052..0x053 then @bldalpha.read_byte(io_addr & 1) when 0x054..0x055 then @bldy.read_byte(io_addr & 1) else log "Unmapped PPU read ~ addr:#{hex_str io_addr.to_u8}"; 0_u8 # todo: open bus + + end end diff --git a/src/crab/gba/reg.cr b/src/crab/gba/reg.cr index 0ddbb5e..eeeb830 100644 --- a/src/crab/gba/reg.cr +++ b/src/crab/gba/reg.cr @@ -228,7 +228,7 @@ module GBA bool bg2_2nd_target_pixel bool bg1_2nd_target_pixel bool bg0_2nd_target_pixel - num color_special_effect, 2 + num blending_mode, 2 bool bd_1st_target_pixel bool obj_1st_target_pixel bool bg3_1st_target_pixel diff --git a/src/crab/gba/types.cr b/src/crab/gba/types.cr index 98cdd6a..98e879a 100644 --- a/src/crab/gba/types.cr +++ b/src/crab/gba/types.cr @@ -3,6 +3,10 @@ module GBA alias HalfWord = UInt16 alias Word = UInt32 alias Words = Slice(UInt32) + + record Color, priority : Int32, palette : Int32, blends : Bool, target_bit : Int32, sprite : Bool do + end + record BGR16, value : UInt16 do # xBBBBBGGGGGRRRRR # Create a new BGR16 struct with the given values. Trucates at 5 bits. def initialize(blue : Number, green : Number, red : Number) @@ -11,6 +15,12 @@ module GBA (red <= 0x1F ? red.to_u16 : 0x1F_u16) end + def initialize(pram : Slice(UInt8), color : Color) + palette_base = pram.to_unsafe + palette_base += 0x200 if color.sprite + @value = palette_base.as(UInt16*)[color.palette] + end + def blue : UInt16 bits(value, 0xA..0xE) end