2022-01-04 01:47:26 +00:00
const std = @import ( " std " ) ;
2022-02-13 08:27:20 +00:00
const io = @import ( " bus/io.zig " ) ;
2022-10-29 02:43:29 +00:00
const util = @import ( " ../util.zig " ) ;
2022-01-04 01:47:26 +00:00
2022-02-16 05:09:06 +00:00
const Bit = @import ( " bitfield " ) . Bit ;
const Bitfield = @import ( " bitfield " ) . Bitfield ;
2022-09-18 10:17:54 +00:00
const dma = @import ( " bus/dma.zig " ) ;
const Oam = @import ( " ppu/Oam.zig " ) ;
const Palette = @import ( " ppu/Palette.zig " ) ;
const Vram = @import ( " ppu/Vram.zig " ) ;
const Scheduler = @import ( " scheduler.zig " ) . Scheduler ;
const Arm7tdmi = @import ( " cpu.zig " ) . Arm7tdmi ;
2022-09-18 10:41:37 +00:00
const FrameBuffer = @import ( " ../util.zig " ) . FrameBuffer ;
2022-02-16 05:09:06 +00:00
2022-01-08 00:00:42 +00:00
const Allocator = std . mem . Allocator ;
2022-03-10 22:48:44 +00:00
const log = std . log . scoped ( . PPU ) ;
2022-10-29 02:43:29 +00:00
2022-10-30 05:36:46 +00:00
const getHalf = util . getHalf ;
const setHalf = util . setHalf ;
const setQuart = util . setQuart ;
2022-03-18 09:27:37 +00:00
2022-01-12 04:46:20 +00:00
pub const width = 240 ;
pub const height = 160 ;
2022-03-18 09:27:37 +00:00
pub const framebuf_pitch = width * @sizeOf ( u32 ) ;
2022-01-04 01:47:26 +00:00
2022-10-29 02:43:29 +00:00
pub fn read ( comptime T : type , ppu : * const Ppu , addr : u32 ) ? T {
2022-10-30 05:15:26 +00:00
const byte_addr = @truncate ( u8 , addr ) ;
2022-10-29 02:43:29 +00:00
return switch ( T ) {
2022-10-30 05:15:26 +00:00
u32 = > switch ( byte_addr ) {
2022-10-29 04:08:58 +00:00
0x00 = > ppu . dispcnt . raw , // Green Swap is in high half-word
2022-10-29 02:43:29 +00:00
0x04 = > @as ( T , ppu . vcount . raw ) < < 16 | ppu . dispstat . raw ,
2022-10-29 08:18:19 +00:00
0x08 = > @as ( T , ppu . bg [ 1 ] . bg1Cnt ( ) ) < < 16 | ppu . bg [ 0 ] . bg0Cnt ( ) ,
2022-10-29 04:08:58 +00:00
0x0C = > @as ( T , ppu . bg [ 3 ] . cnt . raw ) < < 16 | ppu . bg [ 2 ] . cnt . raw ,
2022-10-30 05:15:26 +00:00
0x10 , 0x14 , 0x18 , 0x1C = > null , // BGXHOFS/VOFS
0x20 , 0x24 , 0x28 , 0x2C = > null , // BG2 Rot/Scaling
0x30 , 0x34 , 0x38 , 0x3C = > null , // BG3 Rot/Scaling
0x40 , 0x44 = > null , // WINXH/V Registers
0x48 = > @as ( T , ppu . win . getOut ( ) ) < < 16 | ppu . win . getIn ( ) ,
2022-10-29 04:08:58 +00:00
0x4C = > null , // MOSAIC, undefined in high byte
2022-10-29 08:18:19 +00:00
0x50 = > @as ( T , ppu . bld . getAlpha ( ) ) < < 16 | ppu . bld . getCnt ( ) ,
2022-10-29 04:08:58 +00:00
0x54 = > null , // BLDY, undefined in high half-wrd
2022-10-29 05:37:33 +00:00
else = > util . io . read . err ( T , log , " unaligned {} read from 0x{X:0>8} " , . { T , addr } ) ,
2022-10-29 02:43:29 +00:00
} ,
2022-10-30 05:15:26 +00:00
u16 = > switch ( byte_addr ) {
2022-10-29 02:43:29 +00:00
0x00 = > ppu . dispcnt . raw ,
2022-10-29 04:08:58 +00:00
0x02 = > null , // Green Swap
2022-10-29 02:43:29 +00:00
0x04 = > ppu . dispstat . raw ,
0x06 = > ppu . vcount . raw ,
2022-10-29 08:18:19 +00:00
0x08 = > ppu . bg [ 0 ] . bg0Cnt ( ) ,
0x0A = > ppu . bg [ 1 ] . bg1Cnt ( ) ,
2022-10-29 02:43:29 +00:00
0x0C = > ppu . bg [ 2 ] . cnt . raw ,
0x0E = > ppu . bg [ 3 ] . cnt . raw ,
2022-10-30 05:15:26 +00:00
0x10 , 0x12 , 0x14 , 0x16 , 0x18 , 0x1A , 0x1C , 0x1E = > null , // BGXHOFS/VOFS
0x20 , 0x22 , 0x24 , 0x26 , 0x28 , 0x2A , 0x2C , 0x2E = > null , // BG2 Rot/Scaling
0x30 , 0x32 , 0x34 , 0x36 , 0x38 , 0x3A , 0x3C , 0x3E = > null , // BG3 Rot/Scaling
0x40 , 0x42 , 0x44 , 0x46 = > null , // WINXH/V Registers
0x48 = > ppu . win . getIn ( ) ,
0x4A = > ppu . win . getOut ( ) ,
2022-10-29 04:08:58 +00:00
0x4C = > null , // MOSAIC
2022-10-30 05:15:26 +00:00
0x4E = > null ,
2022-10-29 08:18:19 +00:00
0x50 = > ppu . bld . getCnt ( ) ,
0x52 = > ppu . bld . getAlpha ( ) ,
2022-10-29 04:08:58 +00:00
0x54 = > null , // BLDY
2022-10-29 05:37:33 +00:00
else = > util . io . read . err ( T , log , " unaligned {} read from 0x{X:0>8} " , . { T , addr } ) ,
2022-10-29 02:43:29 +00:00
} ,
2022-10-30 05:15:26 +00:00
u8 = > switch ( byte_addr ) {
0x00 , 0x01 = > @truncate ( T , ppu . dispcnt . raw > > getHalf ( byte_addr ) ) ,
0x02 , 0x03 = > null ,
0x04 , 0x05 = > @truncate ( T , ppu . dispstat . raw > > getHalf ( byte_addr ) ) ,
0x06 , 0x07 = > @truncate ( T , ppu . vcount . raw > > getHalf ( byte_addr ) ) ,
0x08 , 0x09 = > @truncate ( T , ppu . bg [ 0 ] . bg0Cnt ( ) > > getHalf ( byte_addr ) ) ,
0x0A , 0x0B = > @truncate ( T , ppu . bg [ 1 ] . bg1Cnt ( ) > > getHalf ( byte_addr ) ) ,
0x0C , 0x0D = > @truncate ( T , ppu . bg [ 2 ] . cnt . raw > > getHalf ( byte_addr ) ) ,
0x0E , 0x0F = > @truncate ( T , ppu . bg [ 3 ] . cnt . raw > > getHalf ( byte_addr ) ) ,
2022-10-29 04:08:58 +00:00
0x10 . . . 0x1F = > null , // BGXHOFS/VOFS
2022-10-30 05:15:26 +00:00
0x20 . . . 0x2F = > null , // BG2 Rot/Scaling
0x30 . . . 0x3F = > null , // BG3 Rot/Scaling
2022-10-29 04:08:58 +00:00
0x40 . . . 0x47 = > null , // WINXH/V Registers
2022-10-30 05:15:26 +00:00
0x48 , 0x49 = > @truncate ( T , ppu . win . getIn ( ) > > getHalf ( byte_addr ) ) ,
0x4A , 0x4B = > @truncate ( T , ppu . win . getOut ( ) > > getHalf ( byte_addr ) ) ,
0x4C , 0x4D = > null , // MOSAIC
0x4E , 0x4F = > null ,
0x50 , 0x51 = > @truncate ( T , ppu . bld . getCnt ( ) > > getHalf ( byte_addr ) ) ,
0x52 , 0x53 = > @truncate ( T , ppu . bld . getAlpha ( ) > > getHalf ( byte_addr ) ) ,
0x54 , 0x55 = > null , // BLDY
2022-10-29 07:10:19 +00:00
else = > util . io . read . err ( T , log , " unexpected {} read from 0x{X:0>8} " , . { T , addr } ) ,
2022-10-29 02:43:29 +00:00
} ,
else = > @compileError ( " PPU: Unsupported read width " ) ,
} ;
}
pub fn write ( comptime T : type , ppu : * Ppu , addr : u32 , value : T ) void {
2022-10-30 05:36:46 +00:00
const byte_addr = @truncate ( u8 , addr ) ; // prefixed with 0x0400_00
2022-10-29 02:43:29 +00:00
switch ( T ) {
2022-10-30 05:36:46 +00:00
u32 = > switch ( byte_addr ) {
2022-10-29 02:43:29 +00:00
0x00 = > ppu . dispcnt . raw = @truncate ( u16 , value ) ,
0x04 = > {
2022-11-02 10:54:06 +00:00
ppu . dispstat . set ( @truncate ( u16 , value ) ) ;
2022-10-29 02:43:29 +00:00
ppu . vcount . raw = @truncate ( u16 , value > > 16 ) ;
} ,
0x08 = > ppu . setAdjCnts ( 0 , value ) ,
0x0C = > ppu . setAdjCnts ( 2 , value ) ,
2022-10-30 05:36:46 +00:00
2022-10-29 02:43:29 +00:00
0x10 = > ppu . setBgOffsets ( 0 , value ) ,
0x14 = > ppu . setBgOffsets ( 1 , value ) ,
0x18 = > ppu . setBgOffsets ( 2 , value ) ,
0x1C = > ppu . setBgOffsets ( 3 , value ) ,
2022-10-30 05:36:46 +00:00
2022-10-29 02:43:29 +00:00
0x20 = > ppu . aff_bg [ 0 ] . writePaPb ( value ) ,
0x24 = > ppu . aff_bg [ 0 ] . writePcPd ( value ) ,
0x28 = > ppu . aff_bg [ 0 ] . setX ( ppu . dispstat . vblank . read ( ) , value ) ,
0x2C = > ppu . aff_bg [ 0 ] . setY ( ppu . dispstat . vblank . read ( ) , value ) ,
2022-10-30 05:36:46 +00:00
2022-10-29 02:43:29 +00:00
0x30 = > ppu . aff_bg [ 1 ] . writePaPb ( value ) ,
0x34 = > ppu . aff_bg [ 1 ] . writePcPd ( value ) ,
0x38 = > ppu . aff_bg [ 1 ] . setX ( ppu . dispstat . vblank . read ( ) , value ) ,
0x3C = > ppu . aff_bg [ 1 ] . setY ( ppu . dispstat . vblank . read ( ) , value ) ,
2022-10-30 05:36:46 +00:00
2022-10-29 02:43:29 +00:00
0x40 = > ppu . win . setH ( value ) ,
0x44 = > ppu . win . setV ( value ) ,
0x48 = > ppu . win . setIo ( value ) ,
0x4C = > log . debug ( " Wrote 0x{X:0>8} to MOSAIC " , . { value } ) ,
2022-10-30 05:36:46 +00:00
2022-10-29 02:43:29 +00:00
0x50 = > {
2022-10-29 08:18:19 +00:00
ppu . bld . cnt . raw = @truncate ( u16 , value ) ;
ppu . bld . alpha . raw = @truncate ( u16 , value > > 16 ) ;
2022-10-29 02:43:29 +00:00
} ,
2022-10-29 08:18:19 +00:00
0x54 = > ppu . bld . y . raw = @truncate ( u16 , value ) ,
2022-10-29 02:43:29 +00:00
else = > util . io . write . undef ( log , " Tried to write 0x{X:0>8}{} to 0x{X:0>8} " , . { value , T , addr } ) ,
} ,
2022-10-30 05:36:46 +00:00
u16 = > switch ( byte_addr ) {
2022-10-29 02:43:29 +00:00
0x00 = > ppu . dispcnt . raw = value ,
2022-10-30 05:36:46 +00:00
0x02 = > { } , // Green Swap
2022-11-02 10:54:06 +00:00
0x04 = > ppu . dispstat . set ( value ) ,
2022-10-30 05:36:46 +00:00
0x06 = > { } , // VCOUNT
2022-10-29 02:43:29 +00:00
0x08 = > ppu . bg [ 0 ] . cnt . raw = value ,
0x0A = > ppu . bg [ 1 ] . cnt . raw = value ,
0x0C = > ppu . bg [ 2 ] . cnt . raw = value ,
0x0E = > ppu . bg [ 3 ] . cnt . raw = value ,
2022-10-30 05:36:46 +00:00
2022-10-29 02:43:29 +00:00
0x10 = > ppu . bg [ 0 ] . hofs . raw = value , // TODO: Don't write out every HOFS / VOFS?
0x12 = > ppu . bg [ 0 ] . vofs . raw = value ,
0x14 = > ppu . bg [ 1 ] . hofs . raw = value ,
0x16 = > ppu . bg [ 1 ] . vofs . raw = value ,
0x18 = > ppu . bg [ 2 ] . hofs . raw = value ,
0x1A = > ppu . bg [ 2 ] . vofs . raw = value ,
0x1C = > ppu . bg [ 3 ] . hofs . raw = value ,
0x1E = > ppu . bg [ 3 ] . vofs . raw = value ,
2022-10-30 05:36:46 +00:00
2022-10-29 02:43:29 +00:00
0x20 = > ppu . aff_bg [ 0 ] . pa = @bitCast ( i16 , value ) ,
0x22 = > ppu . aff_bg [ 0 ] . pb = @bitCast ( i16 , value ) ,
0x24 = > ppu . aff_bg [ 0 ] . pc = @bitCast ( i16 , value ) ,
0x26 = > ppu . aff_bg [ 0 ] . pd = @bitCast ( i16 , value ) ,
2022-10-30 05:36:46 +00:00
0x28 , 0x2A = > ppu . aff_bg [ 0 ] . x = @bitCast ( i32 , setHalf ( u32 , @bitCast ( u32 , ppu . aff_bg [ 0 ] . x ) , byte_addr , value ) ) ,
0x2C , 0x2E = > ppu . aff_bg [ 0 ] . y = @bitCast ( i32 , setHalf ( u32 , @bitCast ( u32 , ppu . aff_bg [ 0 ] . y ) , byte_addr , value ) ) ,
2022-10-29 02:43:29 +00:00
0x30 = > ppu . aff_bg [ 1 ] . pa = @bitCast ( i16 , value ) ,
0x32 = > ppu . aff_bg [ 1 ] . pb = @bitCast ( i16 , value ) ,
0x34 = > ppu . aff_bg [ 1 ] . pc = @bitCast ( i16 , value ) ,
0x36 = > ppu . aff_bg [ 1 ] . pd = @bitCast ( i16 , value ) ,
2022-10-30 05:36:46 +00:00
0x38 , 0x3A = > ppu . aff_bg [ 1 ] . x = @bitCast ( i32 , setHalf ( u32 , @bitCast ( u32 , ppu . aff_bg [ 1 ] . x ) , byte_addr , value ) ) ,
0x3C , 0x3E = > ppu . aff_bg [ 1 ] . y = @bitCast ( i32 , setHalf ( u32 , @bitCast ( u32 , ppu . aff_bg [ 1 ] . y ) , byte_addr , value ) ) ,
2022-10-29 02:43:29 +00:00
0x40 = > ppu . win . h [ 0 ] . raw = value ,
0x42 = > ppu . win . h [ 1 ] . raw = value ,
0x44 = > ppu . win . v [ 0 ] . raw = value ,
0x46 = > ppu . win . v [ 1 ] . raw = value ,
2022-10-30 05:15:26 +00:00
0x48 = > ppu . win . in . raw = value ,
0x4A = > ppu . win . out . raw = value ,
2022-10-29 02:43:29 +00:00
0x4C = > log . debug ( " Wrote 0x{X:0>4} to MOSAIC " , . { value } ) ,
2022-10-30 05:36:46 +00:00
0x4E = > { } ,
2022-10-29 08:18:19 +00:00
0x50 = > ppu . bld . cnt . raw = value ,
0x52 = > ppu . bld . alpha . raw = value ,
0x54 = > ppu . bld . y . raw = value ,
2022-10-29 02:43:29 +00:00
else = > util . io . write . undef ( log , " Tried to write 0x{X:0>4}{} to 0x{X:0>8} " , . { value , T , addr } ) ,
} ,
2022-10-30 05:36:46 +00:00
u8 = > switch ( byte_addr ) {
0x00 , 0x01 = > ppu . dispcnt . raw = setHalf ( u16 , ppu . dispcnt . raw , byte_addr , value ) ,
0x02 , 0x03 = > { } , // Green Swap
2022-11-02 10:54:06 +00:00
0x04 , 0x05 = > ppu . dispstat . set ( setHalf ( u16 , ppu . dispstat . raw , byte_addr , value ) ) ,
2022-10-30 05:36:46 +00:00
0x06 , 0x07 = > { } , // VCOUNT
// BGXCNT
0x08 , 0x09 = > ppu . bg [ 0 ] . cnt . raw = setHalf ( u16 , ppu . bg [ 0 ] . cnt . raw , byte_addr , value ) ,
0x0A , 0x0B = > ppu . bg [ 1 ] . cnt . raw = setHalf ( u16 , ppu . bg [ 1 ] . cnt . raw , byte_addr , value ) ,
0x0C , 0x0D = > ppu . bg [ 2 ] . cnt . raw = setHalf ( u16 , ppu . bg [ 2 ] . cnt . raw , byte_addr , value ) ,
0x0E , 0x0F = > ppu . bg [ 3 ] . cnt . raw = setHalf ( u16 , ppu . bg [ 3 ] . cnt . raw , byte_addr , value ) ,
// BGX HOFS/VOFS
0x10 , 0x11 = > ppu . bg [ 0 ] . hofs . raw = setHalf ( u16 , ppu . bg [ 0 ] . hofs . raw , byte_addr , value ) ,
0x12 , 0x13 = > ppu . bg [ 0 ] . vofs . raw = setHalf ( u16 , ppu . bg [ 0 ] . vofs . raw , byte_addr , value ) ,
0x14 , 0x15 = > ppu . bg [ 1 ] . hofs . raw = setHalf ( u16 , ppu . bg [ 1 ] . hofs . raw , byte_addr , value ) ,
0x16 , 0x17 = > ppu . bg [ 1 ] . vofs . raw = setHalf ( u16 , ppu . bg [ 1 ] . vofs . raw , byte_addr , value ) ,
0x18 , 0x19 = > ppu . bg [ 2 ] . hofs . raw = setHalf ( u16 , ppu . bg [ 2 ] . hofs . raw , byte_addr , value ) ,
0x1A , 0x1B = > ppu . bg [ 2 ] . vofs . raw = setHalf ( u16 , ppu . bg [ 2 ] . vofs . raw , byte_addr , value ) ,
0x1C , 0x1D = > ppu . bg [ 3 ] . hofs . raw = setHalf ( u16 , ppu . bg [ 3 ] . hofs . raw , byte_addr , value ) ,
0x1E , 0x1F = > ppu . bg [ 3 ] . vofs . raw = setHalf ( u16 , ppu . bg [ 3 ] . vofs . raw , byte_addr , value ) ,
// BG2 Rot/Scaling
0x20 , 0x21 = > ppu . aff_bg [ 0 ] . pa = @bitCast ( i16 , setHalf ( u16 , @bitCast ( u16 , ppu . aff_bg [ 0 ] . pa ) , byte_addr , value ) ) ,
0x22 , 0x23 = > ppu . aff_bg [ 0 ] . pb = @bitCast ( i16 , setHalf ( u16 , @bitCast ( u16 , ppu . aff_bg [ 0 ] . pb ) , byte_addr , value ) ) ,
0x24 , 0x25 = > ppu . aff_bg [ 0 ] . pc = @bitCast ( i16 , setHalf ( u16 , @bitCast ( u16 , ppu . aff_bg [ 0 ] . pc ) , byte_addr , value ) ) ,
0x26 , 0x27 = > ppu . aff_bg [ 0 ] . pd = @bitCast ( i16 , setHalf ( u16 , @bitCast ( u16 , ppu . aff_bg [ 0 ] . pd ) , byte_addr , value ) ) ,
0x28 , 0x29 , 0x2A , 0x2B = > ppu . aff_bg [ 0 ] . x = @bitCast ( i32 , setQuart ( @bitCast ( u32 , ppu . aff_bg [ 0 ] . x ) , byte_addr , value ) ) ,
0x2C , 0x2D , 0x2E , 0x2F = > ppu . aff_bg [ 0 ] . y = @bitCast ( i32 , setQuart ( @bitCast ( u32 , ppu . aff_bg [ 0 ] . y ) , byte_addr , value ) ) ,
// BG3 Rot/Scaling
0x30 , 0x31 = > ppu . aff_bg [ 1 ] . pa = @bitCast ( i16 , setHalf ( u16 , @bitCast ( u16 , ppu . aff_bg [ 1 ] . pa ) , byte_addr , value ) ) ,
0x32 , 0x33 = > ppu . aff_bg [ 1 ] . pb = @bitCast ( i16 , setHalf ( u16 , @bitCast ( u16 , ppu . aff_bg [ 1 ] . pb ) , byte_addr , value ) ) ,
0x34 , 0x35 = > ppu . aff_bg [ 1 ] . pc = @bitCast ( i16 , setHalf ( u16 , @bitCast ( u16 , ppu . aff_bg [ 1 ] . pc ) , byte_addr , value ) ) ,
0x36 , 0x37 = > ppu . aff_bg [ 1 ] . pd = @bitCast ( i16 , setHalf ( u16 , @bitCast ( u16 , ppu . aff_bg [ 1 ] . pd ) , byte_addr , value ) ) ,
0x38 , 0x39 , 0x3A , 0x3B = > ppu . aff_bg [ 1 ] . x = @bitCast ( i32 , setQuart ( @bitCast ( u32 , ppu . aff_bg [ 1 ] . x ) , byte_addr , value ) ) ,
0x3C , 0x3D , 0x3E , 0x3F = > ppu . aff_bg [ 1 ] . y = @bitCast ( i32 , setQuart ( @bitCast ( u32 , ppu . aff_bg [ 1 ] . y ) , byte_addr , value ) ) ,
// Window
0x40 , 0x41 = > ppu . win . h [ 0 ] . raw = setHalf ( u16 , ppu . win . h [ 0 ] . raw , byte_addr , value ) ,
0x42 , 0x43 = > ppu . win . h [ 1 ] . raw = setHalf ( u16 , ppu . win . h [ 1 ] . raw , byte_addr , value ) ,
0x44 , 0x45 = > ppu . win . v [ 0 ] . raw = setHalf ( u16 , ppu . win . v [ 0 ] . raw , byte_addr , value ) ,
0x46 , 0x47 = > ppu . win . v [ 1 ] . raw = setHalf ( u16 , ppu . win . v [ 1 ] . raw , byte_addr , value ) ,
0x48 , 0x49 = > ppu . win . in . raw = setHalf ( u16 , ppu . win . in . raw , byte_addr , value ) ,
0x4A , 0x4B = > ppu . win . out . raw = setHalf ( u16 , ppu . win . out . raw , byte_addr , value ) ,
0x4C , 0x4D = > log . debug ( " Wrote 0x{X:0>2} to MOSAIC " , . { value } ) ,
0x4E , 0x4F = > { } ,
// Blending
0x50 , 0x51 = > ppu . bld . cnt . raw = setHalf ( u16 , ppu . bld . cnt . raw , byte_addr , value ) ,
0x52 , 0x53 = > ppu . bld . alpha . raw = setHalf ( u16 , ppu . bld . alpha . raw , byte_addr , value ) ,
0x54 , 0x55 = > ppu . bld . y . raw = setHalf ( u16 , ppu . bld . y . raw , byte_addr , value ) ,
2022-10-29 02:43:29 +00:00
else = > util . io . write . undef ( log , " Tried to write 0x{X:0>2}{} to 0x{X:0>8} " , . { value , T , addr } ) ,
} ,
else = > @compileError ( " PPU: Unsupported write width " ) ,
}
}
2022-01-04 01:47:26 +00:00
pub const Ppu = struct {
2022-01-10 03:34:33 +00:00
const Self = @This ( ) ;
2022-02-13 08:27:20 +00:00
// Registers
2022-02-16 07:27:06 +00:00
2022-06-29 17:53:35 +00:00
win : Window ,
2022-02-16 07:27:06 +00:00
bg : [ 4 ] Background ,
2022-06-04 12:59:47 +00:00
aff_bg : [ 2 ] AffineBackground ,
2022-02-13 08:27:20 +00:00
dispcnt : io . DisplayControl ,
dispstat : io . DisplayStatus ,
vcount : io . VCount ,
2022-10-29 08:18:19 +00:00
bld : Blend ,
2022-06-19 04:18:25 +00:00
2022-01-04 01:47:26 +00:00
vram : Vram ,
2022-01-04 09:29:56 +00:00
palette : Palette ,
2022-02-13 06:29:26 +00:00
oam : Oam ,
2022-01-05 22:34:42 +00:00
sched : * Scheduler ,
2022-05-17 09:53:37 +00:00
framebuf : FrameBuffer ,
2022-09-03 21:30:48 +00:00
allocator : Allocator ,
2022-01-04 01:47:26 +00:00
2022-08-29 06:05:33 +00:00
scanline_sprites : * [ 128 ] ? Sprite ,
2022-06-20 01:10:56 +00:00
scanline : Scanline ,
2022-03-02 04:59:33 +00:00
2022-08-29 05:32:41 +00:00
pub fn init ( allocator : Allocator , sched : * Scheduler ) ! Self {
2022-11-02 13:40:07 +00:00
sched . push ( . Draw , 240 * 4 ) ; // Add first PPU Event to Scheduler
2022-01-09 00:30:57 +00:00
2022-08-29 06:05:33 +00:00
const sprites = try allocator . create ( [ 128 ] ? Sprite ) ;
2022-09-18 10:43:33 +00:00
std . mem . set ( ? Sprite , sprites , null ) ;
2022-08-29 06:05:33 +00:00
2022-01-10 03:34:33 +00:00
return Self {
2022-08-29 05:32:41 +00:00
. vram = try Vram . init ( allocator ) ,
. palette = try Palette . init ( allocator ) ,
. oam = try Oam . init ( allocator ) ,
2022-01-05 22:34:42 +00:00
. sched = sched ,
2022-09-18 10:41:37 +00:00
. framebuf = try FrameBuffer . init ( allocator , framebuf_pitch * height ) ,
2022-09-03 21:30:48 +00:00
. allocator = allocator ,
2022-02-13 08:27:20 +00:00
// Registers
2022-06-29 17:53:35 +00:00
. win = Window . init ( ) ,
2022-02-16 07:27:06 +00:00
. bg = [ _ ] Background { Background . init ( ) } * * 4 ,
2022-06-04 12:59:47 +00:00
. aff_bg = [ _ ] AffineBackground { AffineBackground . init ( ) } * * 2 ,
2022-10-29 08:18:19 +00:00
. bld = Blend . create ( ) ,
2022-02-13 08:27:20 +00:00
. dispcnt = . { . raw = 0x0000 } ,
. dispstat = . { . raw = 0x0000 } ,
. vcount = . { . raw = 0x0000 } ,
2022-03-02 04:59:33 +00:00
2022-08-29 05:32:41 +00:00
. scanline = try Scanline . init ( allocator ) ,
2022-08-29 06:05:33 +00:00
. scanline_sprites = sprites ,
2022-01-04 01:47:26 +00:00
} ;
}
2022-01-04 02:08:55 +00:00
2022-08-29 05:32:41 +00:00
pub fn deinit ( self : * Self ) void {
2022-09-03 21:30:48 +00:00
self . allocator . destroy ( self . scanline_sprites ) ;
2022-08-29 05:32:41 +00:00
self . framebuf . deinit ( ) ;
self . scanline . deinit ( ) ;
2022-01-04 02:08:55 +00:00
self . vram . deinit ( ) ;
2022-01-08 02:27:08 +00:00
self . palette . deinit ( ) ;
2022-03-22 14:41:17 +00:00
self . oam . deinit ( ) ;
2022-08-29 05:32:41 +00:00
self . * = undefined ;
2022-01-04 02:08:55 +00:00
}
2022-01-09 14:22:50 +00:00
2022-06-20 01:10:56 +00:00
pub fn setBgOffsets ( self : * Self , comptime n : u2 , word : u32 ) void {
2022-03-05 01:53:49 +00:00
self . bg [ n ] . hofs . raw = @truncate ( u16 , word ) ;
self . bg [ n ] . vofs . raw = @truncate ( u16 , word > > 16 ) ;
}
2022-06-20 01:10:56 +00:00
pub fn setAdjCnts ( self : * Self , comptime n : u2 , word : u32 ) void {
2022-03-05 01:53:49 +00:00
self . bg [ n ] . cnt . raw = @truncate ( u16 , word ) ;
self . bg [ n + 1 ] . cnt . raw = @truncate ( u16 , word > > 16 ) ;
}
2022-03-10 22:48:44 +00:00
/// Search OAM for Sprites that might be rendered on this scanline
fn fetchSprites ( self : * Self ) void {
const y = self . vcount . scanline . read ( ) ;
var i : usize = 0 ;
search : while ( i < self . oam . buf . len ) : ( i + = 8 ) {
// Attributes in OAM are 6 bytes long, with 2 bytes of padding
// Grab Attributes from OAM
2022-04-08 04:18:58 +00:00
const attr0 = @bitCast ( Attr0 , self . oam . read ( u16 , i ) ) ;
2022-03-18 12:41:06 +00:00
// Only consider enabled Sprites
2022-06-04 16:55:31 +00:00
if ( attr0 . is_affine . read ( ) or ! attr0 . disabled . read ( ) ) {
2022-04-08 04:18:58 +00:00
const attr1 = @bitCast ( Attr1 , self . oam . read ( u16 , i + 2 ) ) ;
2022-12-17 13:17:34 +00:00
const d = spriteDimensions ( attr0 . shape . read ( ) , attr1 . size . read ( ) ) ;
// Account for double-size affine sprites
const sprite_height = d [ 1 ] < < blk : {
if ( ! attr0 . is_affine . read ( ) ) break : blk 0 ;
const aff_attr0 : AffineAttr0 = . { . raw = attr0 . raw } ;
break : blk if ( aff_attr0 . double_size . read ( ) ) 1 else 0 ;
} ;
2022-03-18 12:41:06 +00:00
// When fetching sprites we only care about ones that could be rendered
// on this scanline
2022-11-02 13:40:07 +00:00
var y_pos : i32 = attr0 . y . read ( ) ;
if ( y_pos > = 160 ) y_pos - = 256 ; // fleroviux's solution to negative positions
2022-03-18 12:41:06 +00:00
// Sprites are expected to be able to wraparound, we perform the same check
// for unsigned and signed values so that we handle all valid sprite positions
2022-11-02 13:40:07 +00:00
if ( y_pos < = y and y < ( y_pos + sprite_height ) ) {
2022-03-18 12:41:06 +00:00
for ( self . scanline_sprites ) | * maybe_sprite | {
if ( maybe_sprite . * = = null ) {
2022-04-08 04:18:58 +00:00
maybe_sprite . * = Sprite . init ( attr0 , attr1 , @bitCast ( Attr2 , self . oam . read ( u16 , i + 4 ) ) ) ;
2022-03-18 12:41:06 +00:00
continue : search ;
}
2022-03-10 22:48:44 +00:00
}
2022-03-18 12:41:06 +00:00
log . err ( " Found more than 128 sprites in OAM Search " , . { } ) ;
unreachable ; // TODO: Is this truly unreachable?
}
2022-03-10 22:48:44 +00:00
}
}
}
2022-06-04 16:55:31 +00:00
fn drawSprites ( self : * Self , layer : u2 ) void {
2022-03-15 02:30:29 +00:00
// Loop over every fetched sprite
2022-06-04 16:55:31 +00:00
for ( self . scanline_sprites ) | maybe_sprite | {
if ( maybe_sprite ) | sprite | {
// Skip this sprite if it isn't on the current priority
if ( sprite . priority ( ) ! = layer ) continue ;
2022-06-04 22:00:47 +00:00
if ( sprite . attr0 . is_affine . read ( ) ) self . drawAffineSprite ( AffineSprite . from ( sprite ) ) else self . drawSprite ( sprite ) ;
2022-03-15 02:30:29 +00:00
} else break ;
2022-03-12 07:46:41 +00:00
}
}
2022-03-10 22:48:44 +00:00
2022-06-04 22:00:47 +00:00
fn drawAffineSprite ( self : * Self , sprite : AffineSprite ) void {
const is_8bpp = sprite . is8bpp ( ) ;
const tile_id : u32 = sprite . tileId ( ) ;
const obj_mapping = self . dispcnt . obj_mapping . read ( ) ;
const tile_row_offset : u32 = if ( is_8bpp ) 8 else 4 ;
const tile_len : u32 = if ( is_8bpp ) 0x40 else 0x20 ;
2022-12-17 13:17:34 +00:00
const double_size = sprite . attr0 . double_size . read ( ) ;
2022-06-04 22:00:47 +00:00
const char_base = 0x4000 * 4 ;
2022-11-02 13:40:07 +00:00
const y = self . vcount . scanline . read ( ) ;
2022-12-17 13:17:34 +00:00
var sprite_x : i16 = sprite . x ( ) ;
if ( sprite_x > = 240 ) sprite_x - = 512 ;
var sprite_y : i16 = sprite . y ( ) ;
if ( sprite_y > = 160 ) sprite_y - = 256 ;
const base = 32 * @as ( u32 , sprite . matrixId ( ) ) ;
const pa = self . oam . read ( u16 , base + 3 * @sizeOf ( u16 ) ) ;
const pb = self . oam . read ( u16 , base + 7 * @sizeOf ( u16 ) ) ;
const pc = self . oam . read ( u16 , base + 11 * @sizeOf ( u16 ) ) ;
const pd = self . oam . read ( u16 , base + 15 * @sizeOf ( u16 ) ) ;
const matrix = @bitCast ( [ 4 ] i16 , [ _ ] u16 { pa , pb , pc , pd } ) ;
const sprite_width = sprite . width < < if ( double_size ) 1 else 0 ;
const sprite_height = sprite . height < < if ( double_size ) 1 else 0 ;
const half_width = sprite_width > > 1 ;
const half_height = sprite_height > > 1 ;
2022-06-04 22:00:47 +00:00
var i : u9 = 0 ;
2022-12-17 13:17:34 +00:00
while ( i < sprite_width ) : ( i + = 1 ) {
// TODO: Something is wrong here
const x = @truncate ( u9 , @bitCast ( u16 , sprite_x + i ) ) ;
if ( x > = width ) continue ;
2022-06-04 22:00:47 +00:00
2022-12-17 13:17:34 +00:00
if ( ! shouldDrawSprite ( self . bld . cnt , & self . scanline , x ) ) continue ;
2022-06-04 22:00:47 +00:00
2022-12-17 13:17:34 +00:00
// Check to see if sprite pixel is in bounds
// TODO: Are any of the checks here redundant?
if ( ! ( sprite_x < = x and x < ( sprite_x + sprite_width ) ) ) continue ;
2022-06-04 22:00:47 +00:00
// Sprite is within bounds and therefore should be rendered
2022-12-17 13:17:34 +00:00
const local_x = @as ( i16 , x ) - sprite_x ;
const local_y = @as ( i16 , y ) - sprite_y ;
var rot_x = ( ( matrix [ 0 ] * % ( local_x - half_width ) + % matrix [ 1 ] * % ( local_y - half_width ) ) > > 8 ) ;
var rot_y = ( ( matrix [ 2 ] * % ( local_x - half_width ) + % matrix [ 3 ] * % ( local_y - half_width ) ) > > 8 ) ;
rot_x + % = half_width > > if ( double_size ) 1 else 0 ;
rot_y + % = half_height > > if ( double_size ) 1 else 0 ;
// Maybe this is the necessary check?
if ( rot_x > = sprite . width or rot_y > = sprite . height or rot_x < 0 or rot_y < 0 ) continue ;
const tile_x = @bitCast ( u16 , rot_x ) ;
const tile_y = @bitCast ( u16 , rot_y ) ;
2022-06-04 22:00:47 +00:00
const col = @truncate ( u3 , tile_x ) ;
2022-12-17 13:17:34 +00:00
const row = @truncate ( u3 , tile_y ) ;
2022-06-04 22:00:47 +00:00
// TODO: Finish that 2D Sprites Test ROM
const tile_base = char_base + ( tile_id * 0x20 ) + ( row * tile_row_offset ) + if ( is_8bpp ) col else col > > 1 ;
const mapping_offset = if ( obj_mapping ) sprite . width > > 3 else if ( is_8bpp ) @as ( u32 , 0x10 ) else 0x20 ;
const tile_offset = ( tile_x > > 3 ) * tile_len + ( tile_y > > 3 ) * tile_len * mapping_offset ;
2022-12-17 13:17:34 +00:00
// TODO: This is not the proper check
// if (tile_base + tile_offset >= self.vram.buf.len) continue;
2022-06-04 22:00:47 +00:00
const tile = self . vram . buf [ tile_base + tile_offset ] ;
const pal_id : u16 = if ( ! is_8bpp ) get4bppTilePalette ( sprite . palBank ( ) , col , tile ) else tile ;
2022-12-17 13:17:34 +00:00
const global_x = @truncate ( u9 , @bitCast ( u16 , local_x + sprite_x ) ) ;
2022-06-04 22:00:47 +00:00
// Sprite Palette starts at 0x0500_0200
2022-06-23 05:31:34 +00:00
if ( pal_id ! = 0 ) {
const bgr555 = self . palette . read ( u16 , 0x200 + pal_id * 2 ) ;
2022-12-17 13:17:34 +00:00
drawSpritePixel ( self . bld . cnt , & self . scanline , global_x , bgr555 ) ;
2022-06-23 05:31:34 +00:00
}
2022-06-04 22:00:47 +00:00
}
}
2022-06-04 16:55:31 +00:00
fn drawSprite ( self : * Self , sprite : Sprite ) void {
2022-06-04 22:00:47 +00:00
const is_8bpp = sprite . is8bpp ( ) ;
const tile_id : u32 = sprite . tileId ( ) ;
2022-06-04 16:55:31 +00:00
const obj_mapping = self . dispcnt . obj_mapping . read ( ) ;
const tile_row_offset : u32 = if ( is_8bpp ) 8 else 4 ;
const tile_len : u32 = if ( is_8bpp ) 0x40 else 0x20 ;
2022-03-10 22:48:44 +00:00
2022-06-04 16:55:31 +00:00
const char_base = 0x4000 * 4 ;
2022-03-10 22:48:44 +00:00
2022-11-02 13:40:07 +00:00
const y = self . vcount . scanline . read ( ) ;
2022-06-04 16:55:31 +00:00
var i : u9 = 0 ;
while ( i < sprite . width ) : ( i + = 1 ) {
const x = ( sprite . x ( ) + % i ) % width ;
2022-10-29 08:18:19 +00:00
if ( ! shouldDrawSprite ( self . bld . cnt , & self . scanline , x ) ) continue ;
2022-03-10 22:48:44 +00:00
2022-11-02 13:40:07 +00:00
var x_pos : i32 = sprite . x ( ) ;
if ( x_pos > = 240 ) x_pos - = 512 ;
2022-03-12 02:40:47 +00:00
2022-11-02 13:40:07 +00:00
if ( ! ( x_pos < = x and x < ( x_pos + sprite . width ) ) ) continue ;
2022-03-12 02:40:47 +00:00
2022-06-04 16:55:31 +00:00
// Sprite is within bounds and therefore should be rendered
2022-11-02 13:40:07 +00:00
const x_diff : i32 = std . math . absInt ( @as ( i32 , x ) - x_pos ) catch unreachable ;
const y_diff : i32 = std . math . absInt ( @bitCast ( i8 , y ) - % @bitCast ( i8 , sprite . y ( ) ) ) catch unreachable ;
2022-03-12 02:40:47 +00:00
2022-06-04 16:55:31 +00:00
// Note that we flip the tile_pos not the (tile_pos % 8) like we do for
// Background Tiles. By doing this we mirror the entire sprite instead of
// just a specific tile (see how sprite.width and sprite.height are involved)
2022-11-02 13:40:07 +00:00
const tile_x = @intCast ( u9 , x_diff ) ^ if ( sprite . hFlip ( ) ) ( sprite . width - 1 ) else 0 ;
const tile_y = @intCast ( u8 , y_diff ) ^ if ( sprite . vFlip ( ) ) ( sprite . height - 1 ) else 0 ;
2022-03-12 02:40:47 +00:00
2022-06-04 16:55:31 +00:00
const row = @truncate ( u3 , tile_y ) ;
const col = @truncate ( u3 , tile_x ) ;
2022-03-12 02:40:47 +00:00
2022-06-04 16:55:31 +00:00
// TODO: Finish that 2D Sprites Test ROM
const tile_base = char_base + ( tile_id * 0x20 ) + ( row * tile_row_offset ) + if ( is_8bpp ) col else col > > 1 ;
const mapping_offset = if ( obj_mapping ) sprite . width > > 3 else if ( is_8bpp ) @as ( u32 , 0x10 ) else 0x20 ;
const tile_offset = ( tile_x > > 3 ) * tile_len + ( tile_y > > 3 ) * tile_len * mapping_offset ;
const tile = self . vram . buf [ tile_base + tile_offset ] ;
2022-06-04 22:00:47 +00:00
const pal_id : u16 = if ( ! is_8bpp ) get4bppTilePalette ( sprite . palBank ( ) , col , tile ) else tile ;
2022-06-04 16:55:31 +00:00
// Sprite Palette starts at 0x0500_0200
2022-06-20 01:10:56 +00:00
if ( pal_id ! = 0 ) {
const bgr555 = self . palette . read ( u16 , 0x200 + pal_id * 2 ) ;
2022-11-17 16:16:35 +00:00
drawSpritePixel ( self . bld . cnt , & self . scanline , x , bgr555 ) ;
2022-06-20 01:10:56 +00:00
}
2022-06-04 16:55:31 +00:00
}
2022-03-10 22:48:44 +00:00
}
2022-06-20 01:10:56 +00:00
fn drawAffineBackground ( self : * Self , comptime n : u2 ) void {
2022-06-03 22:26:52 +00:00
comptime std . debug . assert ( n = = 2 or n = = 3 ) ; // Only BG2 and BG3 can be affine
2022-06-04 12:49:34 +00:00
const char_base = @as ( u32 , 0x4000 ) * self . bg [ n ] . cnt . char_base . read ( ) ;
const screen_base = @as ( u32 , 0x800 ) * self . bg [ n ] . cnt . screen_base . read ( ) ;
const size : u2 = self . bg [ n ] . cnt . size . read ( ) ;
const tile_width = @as ( i32 , 0x10 ) < < size ;
const px_width = tile_width < < 3 ;
const px_height = px_width ;
2022-06-04 12:59:47 +00:00
var aff_x = self . aff_bg [ n - 2 ] . x_latch . ? ;
var aff_y = self . aff_bg [ n - 2 ] . y_latch . ? ;
2022-06-04 12:49:34 +00:00
var i : u32 = 0 ;
while ( i < width ) : ( i + = 1 ) {
var ix = aff_x > > 8 ;
var iy = aff_y > > 8 ;
2022-06-04 12:59:47 +00:00
aff_x + = self . aff_bg [ n - 2 ] . pa ;
aff_y + = self . aff_bg [ n - 2 ] . pc ;
2022-06-04 12:49:34 +00:00
2022-07-19 23:43:21 +00:00
const x = @bitCast ( u32 , ix ) ;
const y = @bitCast ( u32 , iy ) ;
const win_bounds = self . windowBounds ( @truncate ( u9 , x ) , @truncate ( u8 , y ) ) ;
if ( ! shouldDrawBackground ( self , n , win_bounds , i ) ) continue ;
2022-06-04 12:49:34 +00:00
if ( self . bg [ n ] . cnt . display_overflow . read ( ) ) {
ix = if ( ix > px_width ) @rem ( ix , px_width ) else if ( ix < 0 ) px_width + @rem ( ix , px_width ) else ix ;
iy = if ( iy > px_height ) @rem ( iy , px_height ) else if ( iy < 0 ) px_height + @rem ( iy , px_height ) else iy ;
} else if ( ix > px_width or iy > px_height or ix < 0 or iy < 0 ) continue ;
const tile_id : u32 = self . vram . read ( u8 , screen_base + ( ( y / 8 ) * @bitCast ( u32 , tile_width ) + ( x / 8 ) ) ) ;
const row = y & 7 ;
const col = x & 7 ;
const tile_addr = char_base + ( tile_id * 0x40 ) + ( row * 0x8 ) + col ;
const pal_id : u16 = self . vram . buf [ tile_addr ] ;
2022-11-17 16:16:35 +00:00
if ( pal_id ! = 0 ) self . drawBackgroundPixel ( n , i , self . palette . read ( u16 , pal_id * 2 ) ) ;
2022-06-04 12:49:34 +00:00
}
// Update BGxX and BGxY
2022-06-04 12:59:47 +00:00
self . aff_bg [ n - 2 ] . x_latch . ? + = self . aff_bg [ n - 2 ] . pb ; // PB is added to BGxX
self . aff_bg [ n - 2 ] . y_latch . ? + = self . aff_bg [ n - 2 ] . pd ; // PD is added to BGxY
2022-06-03 22:26:52 +00:00
}
2022-07-08 19:43:04 +00:00
fn drawBackground ( self : * Self , comptime n : u2 ) void {
2022-02-16 07:27:06 +00:00
// A Tile in a charblock is a byte, while a Screen Entry is a halfword
2022-06-04 12:59:47 +00:00
const char_base = 0x4000 * @as ( u32 , self . bg [ n ] . cnt . char_base . read ( ) ) ;
const screen_base = 0x800 * @as ( u32 , self . bg [ n ] . cnt . screen_base . read ( ) ) ;
2022-02-16 07:27:06 +00:00
const is_8bpp : bool = self . bg [ n ] . cnt . colour_mode . read ( ) ; // Colour Mode
const size : u2 = self . bg [ n ] . cnt . size . read ( ) ; // Background Size
// In 4bpp: 1 byte represents two pixels so the length is (8 x 8) / 2
// In 8bpp: 1 byte represents one pixel so the length is 8 x 8
const tile_len = if ( is_8bpp ) @as ( u32 , 0x40 ) else 0x20 ;
const tile_row_offset = if ( is_8bpp ) @as ( u32 , 0x8 ) else 0x4 ;
2022-03-03 03:15:10 +00:00
const vofs : u32 = self . bg [ n ] . vofs . offset . read ( ) ;
const hofs : u32 = self . bg [ n ] . hofs . offset . read ( ) ;
2022-02-16 07:37:25 +00:00
2022-03-02 04:59:33 +00:00
const y = vofs + self . vcount . scanline . read ( ) ;
2022-02-16 07:37:25 +00:00
var i : u32 = 0 ;
while ( i < width ) : ( i + = 1 ) {
2022-02-17 03:49:08 +00:00
const x = hofs + i ;
2022-02-16 07:27:06 +00:00
2022-07-19 23:43:21 +00:00
const win_bounds = self . windowBounds ( @truncate ( u9 , x ) , @truncate ( u8 , y ) ) ;
if ( ! shouldDrawBackground ( self , n , win_bounds , i ) ) continue ;
2022-07-08 21:17:23 +00:00
2022-02-16 07:27:06 +00:00
// Grab the Screen Entry from VRAM
const entry_addr = screen_base + tilemapOffset ( size , x , y ) ;
2022-04-08 03:44:52 +00:00
const entry = @bitCast ( ScreenEntry , self . vram . read ( u16 , entry_addr ) ) ;
2022-02-16 07:27:06 +00:00
// Calculate the Address of the Tile in the designated Charblock
// We also take this opportunity to flip tiles if necessary
const tile_id : u32 = entry . tile_id . read ( ) ;
2022-06-04 12:59:47 +00:00
// Calculate row and column offsets. Understand that
// `tile_len`, `tile_row_offset` and `col` are subject to different
// values depending on whether we are in 4bpp or 8bpp mode.
const row = @truncate ( u3 , y ) ^ if ( entry . v_flip . read ( ) ) 7 else @as ( u3 , 0 ) ;
2022-05-25 13:10:57 +00:00
const col = @truncate ( u3 , x ) ^ if ( entry . h_flip . read ( ) ) 7 else @as ( u3 , 0 ) ;
2022-06-04 12:59:47 +00:00
const tile_addr = char_base + ( tile_id * tile_len ) + ( row * tile_row_offset ) + if ( is_8bpp ) col else col > > 1 ;
const tile = self . vram . buf [ tile_addr ] ;
2022-02-16 07:27:06 +00:00
// If we're in 8bpp, then the tile value is an index into the palette,
// If we're in 4bpp, we have to account for a pal bank value in the Screen entry
// and then we can index the palette
2022-05-25 13:10:57 +00:00
const pal_id : u16 = if ( ! is_8bpp ) get4bppTilePalette ( entry . pal_bank . read ( ) , col , tile ) else tile ;
2022-02-16 07:27:06 +00:00
2022-11-17 16:16:35 +00:00
if ( pal_id ! = 0 ) self . drawBackgroundPixel ( n , i , self . palette . read ( u16 , pal_id * 2 ) ) ;
2022-02-16 07:27:06 +00:00
}
}
2022-05-25 13:10:57 +00:00
inline fn get4bppTilePalette ( pal_bank : u4 , col : u3 , tile : u8 ) u8 {
const nybble_tile = tile > > ( ( col & 1 ) < < 2 ) & 0xF ;
if ( nybble_tile = = 0 ) return 0 ;
return ( @as ( u8 , pal_bank ) < < 4 ) | nybble_tile ;
}
2022-02-13 08:27:20 +00:00
pub fn drawScanline ( self : * Self ) void {
const bg_mode = self . dispcnt . bg_mode . read ( ) ;
2022-02-16 07:27:06 +00:00
const bg_enable = self . dispcnt . bg_enable . read ( ) ;
2022-03-10 22:48:44 +00:00
const obj_enable = self . dispcnt . obj_enable . read ( ) ;
2022-02-13 08:27:20 +00:00
const scanline = self . vcount . scanline . read ( ) ;
2022-01-09 14:22:50 +00:00
switch ( bg_mode ) {
2022-02-13 08:04:10 +00:00
0x0 = > {
2022-11-03 10:51:06 +00:00
const framebuf_base = width * @as ( usize , scanline ) ;
2022-03-15 02:30:29 +00:00
if ( obj_enable ) self . fetchSprites ( ) ;
2022-03-10 22:48:44 +00:00
2022-05-01 21:09:35 +00:00
var layer : usize = 0 ;
while ( layer < 4 ) : ( layer + = 1 ) {
self . drawSprites ( @truncate ( u2 , layer ) ) ;
2022-07-08 19:43:04 +00:00
if ( layer = = self . bg [ 0 ] . cnt . priority . read ( ) and bg_enable & 1 = = 1 ) self . drawBackground ( 0 ) ;
if ( layer = = self . bg [ 1 ] . cnt . priority . read ( ) and bg_enable > > 1 & 1 = = 1 ) self . drawBackground ( 1 ) ;
if ( layer = = self . bg [ 2 ] . cnt . priority . read ( ) and bg_enable > > 2 & 1 = = 1 ) self . drawBackground ( 2 ) ;
if ( layer = = self . bg [ 3 ] . cnt . priority . read ( ) and bg_enable > > 3 & 1 = = 1 ) self . drawBackground ( 3 ) ;
2022-03-02 04:59:33 +00:00
}
2022-11-03 09:59:30 +00:00
self . drawTextMode ( framebuf_base ) ;
2022-05-01 21:53:11 +00:00
} ,
0x1 = > {
2022-11-03 10:51:06 +00:00
const framebuf_base = width * @as ( usize , scanline ) ;
2022-05-01 21:53:11 +00:00
if ( obj_enable ) self . fetchSprites ( ) ;
var layer : usize = 0 ;
while ( layer < 4 ) : ( layer + = 1 ) {
self . drawSprites ( @truncate ( u2 , layer ) ) ;
2022-07-08 19:43:04 +00:00
if ( layer = = self . bg [ 0 ] . cnt . priority . read ( ) and bg_enable & 1 = = 1 ) self . drawBackground ( 0 ) ;
if ( layer = = self . bg [ 1 ] . cnt . priority . read ( ) and bg_enable > > 1 & 1 = = 1 ) self . drawBackground ( 1 ) ;
2022-06-04 12:49:34 +00:00
if ( layer = = self . bg [ 2 ] . cnt . priority . read ( ) and bg_enable > > 2 & 1 = = 1 ) self . drawAffineBackground ( 2 ) ;
2022-05-01 21:53:11 +00:00
}
2022-11-03 09:59:30 +00:00
self . drawTextMode ( framebuf_base ) ;
2022-05-01 21:53:11 +00:00
} ,
0x2 = > {
2022-11-03 10:51:06 +00:00
const framebuf_base = width * @as ( usize , scanline ) ;
2022-05-01 21:53:11 +00:00
if ( obj_enable ) self . fetchSprites ( ) ;
var layer : usize = 0 ;
while ( layer < 4 ) : ( layer + = 1 ) {
self . drawSprites ( @truncate ( u2 , layer ) ) ;
2022-06-04 12:49:34 +00:00
if ( layer = = self . bg [ 2 ] . cnt . priority . read ( ) and bg_enable > > 2 & 1 = = 1 ) self . drawAffineBackground ( 2 ) ;
if ( layer = = self . bg [ 3 ] . cnt . priority . read ( ) and bg_enable > > 3 & 1 = = 1 ) self . drawAffineBackground ( 3 ) ;
2022-05-01 21:53:11 +00:00
}
2022-11-03 09:59:30 +00:00
self . drawTextMode ( framebuf_base ) ;
2022-02-13 08:04:10 +00:00
} ,
2022-01-09 14:22:50 +00:00
0x3 = > {
2022-11-03 10:38:46 +00:00
const vram_base = width * @as ( usize , scanline ) ;
const framebuf_base = width * @as ( usize , scanline ) ;
2022-03-18 09:27:37 +00:00
2022-11-03 10:38:46 +00:00
// FIXME: @ptrCast between slices changing the length isn't implemented yet
const vram_buf = @ptrCast ( [ * ] const u16 , @alignCast ( @alignOf ( u16 ) , self . vram . buf ) ) ;
const framebuf = @ptrCast ( [ * ] u32 , @alignCast ( @alignOf ( u32 ) , self . framebuf . get ( . Emulator ) ) ) ;
for ( vram_buf [ vram_base . . vram_base + width ] ) | bgr555 , i | {
framebuf [ framebuf_base + i ] = rgba888 ( bgr555 ) ;
2022-03-18 09:27:37 +00:00
}
2022-01-09 14:22:50 +00:00
} ,
2022-01-10 06:41:48 +00:00
0x4 = > {
2022-05-01 21:09:35 +00:00
const sel = self . dispcnt . frame_select . read ( ) ;
2022-11-03 10:38:46 +00:00
2022-05-01 21:09:35 +00:00
const vram_base = width * @as ( usize , scanline ) + if ( sel ) 0xA000 else @as ( usize , 0 ) ;
2022-11-03 10:38:46 +00:00
const framebuf_base = width * @as ( usize , scanline ) ;
// FIXME: @ptrCast between slices changing the length isn't implemented yet
const pal_buf = @ptrCast ( [ * ] const u16 , @alignCast ( @alignOf ( u16 ) , self . palette . buf ) ) ;
const framebuf = @ptrCast ( [ * ] u32 , @alignCast ( @alignOf ( u32 ) , self . framebuf . get ( . Emulator ) ) ) ;
2022-01-10 06:41:48 +00:00
2022-11-03 10:38:46 +00:00
for ( self . vram . buf [ vram_base . . vram_base + width ] ) | pal_id , i | {
framebuf [ framebuf_base + i ] = rgba888 ( pal_buf [ pal_id ] ) ;
2022-05-01 21:09:35 +00:00
}
} ,
0x5 = > {
const m5_width = 160 ;
const m5_height = 128 ;
const sel = self . dispcnt . frame_select . read ( ) ;
2022-11-03 10:38:46 +00:00
const vram_base = m5_width * @as ( usize , scanline ) + if ( sel ) 0xA000 else @as ( usize , 0 ) ;
const framebuf_base = width * @as ( usize , scanline ) ;
// FIXME: @ptrCast between slices changing the length isn't implemented yet
const vram_buf = @ptrCast ( [ * ] const u16 , @alignCast ( @alignOf ( u16 ) , self . vram . buf ) ) ;
const framebuf = @ptrCast ( [ * ] u32 , @alignCast ( @alignOf ( u32 ) , self . framebuf . get ( . Emulator ) ) ) ;
2022-05-01 21:09:35 +00:00
var i : usize = 0 ;
while ( i < width ) : ( i + = 1 ) {
2022-11-03 10:38:46 +00:00
const bgr555 = if ( scanline < m5_height and i < m5_width ) vram_buf [ vram_base + i ] else self . palette . backdrop ( ) ;
framebuf [ framebuf_base + i ] = rgba888 ( bgr555 ) ;
2022-01-10 06:41:48 +00:00
}
} ,
2022-02-13 08:04:10 +00:00
else = > std . debug . panic ( " [PPU] TODO: Implement BG Mode {} " , . { bg_mode } ) ,
2022-01-09 14:22:50 +00:00
}
}
2022-02-16 05:09:06 +00:00
2022-11-03 09:59:30 +00:00
fn drawTextMode ( self : * Self , framebuf_base : usize ) void {
// Copy Drawn Scanline to Frame Buffer
// If there are any nulls present in self.scanline.top() it means that no background drew a pixel there, so draw backdrop
2022-11-03 10:51:06 +00:00
// FIXME: @ptrCast between slices changing the length isn't implemented yet
const framebuf = @ptrCast ( [ * ] u32 , @alignCast ( @alignOf ( u32 ) , self . framebuf . get ( . Emulator ) ) ) ;
2022-11-17 16:16:35 +00:00
for ( self . scanline . top ( ) ) | maybe_top , i | {
2022-11-03 09:59:30 +00:00
const maybe_btm = self . scanline . btm ( ) [ i ] ;
const bgr555 = self . getBgr555 ( maybe_top , maybe_btm ) ;
2022-11-03 10:51:06 +00:00
framebuf [ framebuf_base + i ] = rgba888 ( bgr555 ) ;
2022-11-03 09:59:30 +00:00
}
// Reset Current Scanline Pixel Buffer and list of fetched sprites
// in prep for next scanline
self . scanline . reset ( ) ;
std . mem . set ( ? Sprite , self . scanline_sprites , null ) ;
}
2022-11-17 16:16:35 +00:00
fn getBgr555 ( self : * Self , maybe_top : Scanline . Pixel , maybe_btm : Scanline . Pixel ) u16 {
return switch ( self . bld . cnt . mode . read ( ) ) {
0b00 = > switch ( maybe_top ) {
. set = > | top | top ,
else = > self . palette . backdrop ( ) ,
} ,
0b01 = > switch ( maybe_top ) {
. set = > | top | switch ( maybe_btm ) {
. set = > | btm | alphaBlend ( top , btm , self . bld . alpha ) , // ALPHA_BLEND
else = > top ,
} ,
else = > switch ( maybe_btm ) {
. set = > | btm | btm ,
else = > self . palette . backdrop ( ) ,
} ,
} ,
0b10 = > switch ( maybe_btm ) {
. set = > | btm | blk : {
// BLD_WHITE
2022-10-29 08:18:19 +00:00
const evy : u16 = self . bld . y . evy . read ( ) ;
2022-06-23 07:50:04 +00:00
const r = btm & 0x1F ;
const g = ( btm > > 5 ) & 0x1F ;
const b = ( btm > > 10 ) & 0x1F ;
const bld_r = r + ( ( ( 31 - r ) * evy ) > > 4 ) ;
const bld_g = g + ( ( ( 31 - g ) * evy ) > > 4 ) ;
const bld_b = b + ( ( ( 31 - b ) * evy ) > > 4 ) ;
break : blk ( bld_b < < 10 ) | ( bld_g < < 5 ) | bld_r ;
} ,
2022-11-17 16:16:35 +00:00
else = > switch ( maybe_top ) {
. set = > | top | top ,
else = > self . palette . backdrop ( ) ,
} ,
} ,
0b11 = > switch ( maybe_btm ) {
. set = > | btm | blk : {
// BLD_BLACK
2022-10-29 08:18:19 +00:00
const evy : u16 = self . bld . y . evy . read ( ) ;
2022-06-23 07:50:04 +00:00
2022-11-17 16:16:35 +00:00
const r = btm & 0x1F ;
const g = ( btm > > 5 ) & 0x1F ;
const b = ( btm > > 10 ) & 0x1F ;
2022-06-23 07:50:04 +00:00
2022-11-17 16:16:35 +00:00
const bld_r = r - ( ( r * evy ) > > 4 ) ;
const bld_g = g - ( ( g * evy ) > > 4 ) ;
const bld_b = b - ( ( b * evy ) > > 4 ) ;
2022-06-23 07:50:04 +00:00
break : blk ( bld_b < < 10 ) | ( bld_g < < 5 ) | bld_r ;
} ,
2022-11-17 16:16:35 +00:00
else = > switch ( maybe_top ) {
. set = > | top | top ,
else = > self . palette . backdrop ( ) ,
} ,
} ,
} ;
2022-06-23 07:50:04 +00:00
}
2022-11-17 16:16:35 +00:00
fn drawBackgroundPixel ( self : * Self , comptime layer : u2 , i : usize , bgr555 : u16 ) void {
// When writing to the scanline buffer, we want to be aware of a top and bottom layer. Some preconditions were
// already determined by shouldDrawBackground, so we should be aware of what we can assume to be true or false
2022-07-19 23:43:21 +00:00
2022-11-17 16:16:35 +00:00
switch ( self . bld . cnt . mode . read ( ) ) {
0b00 = > { } , // pass through
0b01 = > {
// We are to alpha blend here so we should pay attention to which layer ths pixel should be written to
// FIXME: We redo work here that we've already figured out. Is this worth refactorning?
2022-07-19 23:43:21 +00:00
2022-11-17 16:16:35 +00:00
// If the current layer is makred as Layer A, write to top buffer
const top_layer = self . bld . cnt . layer_a . read ( ) ;
const is_top_layer = ( top_layer > > layer ) & 1 = = 1 ;
2022-07-19 23:43:21 +00:00
2022-11-17 16:16:35 +00:00
if ( is_top_layer ) {
self . scanline . top ( ) [ i ] = Scanline . Pixel . from ( bgr555 ) ;
return ;
}
// If the current layer is marked as Layer B, we want to continue if there's an available space on that buffer
const btm_layer = self . bld . cnt . layer_b . read ( ) ;
const is_btm_layer = ( btm_layer > > layer ) & 1 = = 1 ;
if ( is_btm_layer ) {
self . scanline . btm ( ) [ i ] = Scanline . Pixel . from ( bgr555 ) ;
return ;
}
// The code we're about to fall-through to assumes that alpha blending takes place. In order to withold all invariants
// we need to discard anything that might be in the bottom buffer.
self . scanline . btm ( ) [ i ] = . hidden ;
} ,
0b10 , 0b11 = > {
// BLD_WHITE, BLD_BLACK
// Weare to blend with White or black here. By convention we store regular ol' pixels in the top layer, which means that if we want to
// treat some pixels (in this case the ones relegated to blending) we need to keep them separate as we can't apply the blending to the top layer.
// While in these modes, (and since this is a scanline renderer), the bottom layer will be completely unused. While it's a bit unintuitive, since we'll
// be moving layer A pixels there, we will repurpose the bottom layer as the "to blend", layer
// If the current layer is makred as Layer A, write to top buffer
const top_layer = self . bld . cnt . layer_a . read ( ) ;
const is_top_layer = ( top_layer > > layer ) & 1 = = 1 ;
if ( is_top_layer ) {
self . scanline . btm ( ) [ i ] = Scanline . Pixel . from ( bgr555 ) ; // this is intentional
return ;
}
} ,
2022-07-19 23:43:21 +00:00
}
2022-11-17 16:16:35 +00:00
// If we aren't blending here at all, just add the pixel to the top layer
self . scanline . top ( ) [ i ] = Scanline . Pixel . from ( bgr555 ) ;
2022-07-19 23:43:21 +00:00
}
const WindowBounds = enum { win0 , win1 , out } ;
fn windowBounds ( self : * Self , x : u9 , y : u8 ) ? WindowBounds {
2022-12-17 13:53:40 +00:00
_ = y ;
_ = x ;
_ = self ;
// FIXME: Remove to enable PPU Window Emulation
return null ;
2022-07-19 23:43:21 +00:00
2022-12-17 13:53:40 +00:00
// const win0 = self.dispcnt.win_enable.read() & 1 == 1;
// const win1 = (self.dispcnt.win_enable.read() >> 1) & 1 == 1;
// const winObj = self.dispcnt.obj_win_enable.read();
2022-07-19 23:43:21 +00:00
2022-12-17 13:53:40 +00:00
// if (!(win0 or win1 or winObj)) return null;
2022-07-19 23:43:21 +00:00
2022-12-17 13:53:40 +00:00
// if (win0 and self.win.inRange(0, x, y)) return .win0;
// if (win1 and self.win.inRange(1, x, y)) return .win1;
// return .out;
2022-07-19 23:43:21 +00:00
}
2022-11-17 16:16:35 +00:00
fn shouldDrawBackground ( self : * Self , comptime layer : u2 , bounds : ? WindowBounds , i : usize ) bool {
switch ( self . bld . cnt . mode . read ( ) ) {
0b00 = > if ( self . scanline . top ( ) [ i ] = = . set ) return false , // pass through
0b01 = > blk : {
// BLD_ALPHA
2022-07-19 23:43:21 +00:00
2022-11-17 16:16:35 +00:00
// If the current layer is marked as Layer B, we want to continue if there's an available space on that buffer
const btm_layer = self . bld . cnt . layer_b . read ( ) ;
const is_btm_layer = ( btm_layer > > layer ) & 1 = = 1 ;
2022-07-19 23:43:21 +00:00
2022-11-17 16:16:35 +00:00
if ( is_btm_layer ) {
if ( self . scanline . btm ( ) [ i ] = = . set ) return false ;
2022-07-19 23:43:21 +00:00
2022-11-17 16:16:35 +00:00
// In some previous iteration we have determined that an opaque pixel was drawn at this position
// therefore there's no reason to draw anything here
if ( self . scanline . btm ( ) [ i ] = = . hidden ) return false ;
2022-07-19 23:43:21 +00:00
2022-11-17 16:16:35 +00:00
// We have a pixel and we know it to be a part of hte bottom layer.
// when getBgr555 sees that thre's a pixel in the top and bottom layer it chooses to blend the two
// Meaning that if we want to prevent Alpha Blending from happening (like for example if a window is preventing it)
// we need to make that happen now.
2022-07-19 23:43:21 +00:00
2022-11-17 16:16:35 +00:00
// We can do this by not drawing the bottom pixel, since with alpha blending disabled it wouldn't be visible anyways
2022-07-19 23:43:21 +00:00
2022-11-17 16:16:35 +00:00
// if (bounds) |win| {
// switch (win) {
// .win0 => if (!self.win.in.w0_bld.read()) return false,
// .win1 => if (!self.win.in.w1_bld.read()) return false,
// .out => if (!self.win.out.out_bld.read()) return false,
// }
// }
2022-07-19 23:43:21 +00:00
2022-11-17 16:16:35 +00:00
break : blk ;
}
if ( self . scanline . top ( ) [ i ] = = . set ) return false ;
} ,
0b10 , 0b11 = > {
// BLD_WHITE and BLD_BLACK
// we want to treat the bottom layer the same as the top (despite it being repurposed)
// so we should apply the same logic to the bottom layer
if ( self . scanline . top ( ) [ i ] = = . set ) return false ;
if ( self . scanline . btm ( ) [ i ] = = . set ) return false ;
} ,
2022-07-19 23:43:21 +00:00
}
2022-11-17 16:16:35 +00:00
// At this point we will have exited early if we determined that we'd be overwriting a pixel
// with a higher priority. We can now move own to determining whether the pixel is visible or not
// The first thing that may or may not affect visibility is windowing. We should check to see if ths pixel is in bounds
// of of the background Window if it is enabled
// TODO: Do Window Bounds checking here instead of outside this function?
if ( bounds ) | window | {
// If this parameter is non-null, we know that:
// 1. Win0, Win1 or WinObj are enabled
// 2. This specific pixel exists within the range of a window
// Here, we check to see if the Window for this background is enabled. If not, we won't render the pixel
// FIXME: We perform needless computations on Window Bounds by checking for enable here after we've already computed this information
switch ( window ) {
. win0 = > if ( ( self . win . in . w0_bg . read ( ) > > layer ) & 1 = = 0 ) return false ,
. win1 = > if ( ( self . win . in . w1_bg . read ( ) > > layer ) & 1 = = 0 ) return false ,
. out = > if ( ( self . win . out . out_bg . read ( ) > > layer ) & 1 = = 0 ) return false ,
}
}
// Otherwise, return true
2022-07-19 23:43:21 +00:00
return true ;
}
2022-06-04 12:59:47 +00:00
// TODO: Comment this + get a better understanding
2022-02-16 06:49:36 +00:00
fn tilemapOffset ( size : u2 , x : u32 , y : u32 ) u32 {
// Current Row: (y % PIXEL_COUNT) / 8
// Current COlumn: (x % PIXEL_COUNT) / 8
// Length of 1 row of Screen Entries: 0x40
// Length of 1 Screen Entry: 0x2 is the size of a screen entry
2022-02-17 03:49:08 +00:00
@setRuntimeSafety ( false ) ;
2022-02-16 05:09:06 +00:00
return switch ( size ) {
2022-02-17 03:49:08 +00:00
0 = > ( x % 256 / 8 ) * 2 + ( y % 256 / 8 ) * 0x40 , // 256 x 256
1 = > blk : {
// 512 x 256
const offset : u32 = if ( x & 0x1FF > 0xFF ) 0x800 else 0 ;
break : blk offset + ( x % 256 / 8 ) * 2 + ( y % 256 / 8 ) * 0x40 ;
} ,
2 = > blk : {
// 256 x 512
const offset : u32 = if ( y & 0x1FF > 0xFF ) 0x800 else 0 ;
break : blk offset + ( x % 256 / 8 ) * 2 + ( y % 256 / 8 ) * 0x40 ;
} ,
3 = > blk : {
// 512 x 512
const offset : u32 = if ( x & 0x1FF > 0xFF ) 0x800 else 0 ;
const offset_2 : u32 = if ( y & 0x1FF > 0xFF ) 0x800 else 0 ;
break : blk offset + offset_2 + ( x % 256 / 8 ) * 2 + ( y % 512 / 8 ) * 0x40 ;
} ,
2022-02-16 05:09:06 +00:00
} ;
}
2022-03-15 07:34:13 +00:00
2022-10-11 00:06:29 +00:00
pub fn onHdrawEnd ( self : * Self , cpu : * Arm7tdmi , late : u64 ) void {
2022-03-15 07:34:13 +00:00
// Transitioning to a Hblank
if ( self . dispstat . hblank_irq . read ( ) ) {
cpu . bus . io . irq . hblank . set ( ) ;
cpu . handleInterrupt ( ) ;
}
// See if HBlank DMA is present and not enabled
2022-06-23 08:45:52 +00:00
if ( ! self . dispstat . vblank . read ( ) )
2022-09-18 10:17:54 +00:00
dma . onBlanking ( cpu . bus , . HBlank ) ;
2022-03-15 07:34:13 +00:00
self . dispstat . hblank . set ( ) ;
2022-05-20 19:01:12 +00:00
self . sched . push ( . HBlank , 68 * 4 - | late ) ;
2022-03-15 07:34:13 +00:00
}
2022-10-11 00:06:29 +00:00
pub fn onHblankEnd ( self : * Self , cpu : * Arm7tdmi , late : u64 ) void {
2022-03-15 07:34:13 +00:00
// The End of a Hblank (During Draw or Vblank)
const old_scanline = self . vcount . scanline . read ( ) ;
const scanline = ( old_scanline + 1 ) % 228 ;
self . vcount . scanline . write ( scanline ) ;
self . dispstat . hblank . unset ( ) ;
// Perform Vc == VcT check
const coincidence = scanline = = self . dispstat . vcount_trigger . read ( ) ;
self . dispstat . coincidence . write ( coincidence ) ;
if ( coincidence and self . dispstat . vcount_irq . read ( ) ) {
cpu . bus . io . irq . coincidence . set ( ) ;
cpu . handleInterrupt ( ) ;
}
if ( scanline < 160 ) {
// Transitioning to another Draw
2022-05-20 19:01:12 +00:00
self . sched . push ( . Draw , 240 * 4 - | late ) ;
2022-03-15 07:34:13 +00:00
} else {
// Transitioning to a Vblank
if ( scanline = = 160 ) {
2022-05-17 09:53:37 +00:00
self . framebuf . swap ( ) ; // Swap FrameBuffers
2022-03-15 07:34:13 +00:00
self . dispstat . vblank . set ( ) ;
if ( self . dispstat . vblank_irq . read ( ) ) {
cpu . bus . io . irq . vblank . set ( ) ;
cpu . handleInterrupt ( ) ;
}
2022-06-04 12:59:47 +00:00
self . aff_bg [ 0 ] . latchRefPoints ( ) ;
self . aff_bg [ 1 ] . latchRefPoints ( ) ;
2022-06-03 22:26:52 +00:00
2022-03-15 07:34:13 +00:00
// See if Vblank DMA is present and not enabled
2022-09-18 10:17:54 +00:00
dma . onBlanking ( cpu . bus , . VBlank ) ;
2022-03-15 07:34:13 +00:00
}
if ( scanline = = 227 ) self . dispstat . vblank . unset ( ) ;
2022-05-20 19:01:12 +00:00
self . sched . push ( . VBlank , 240 * 4 - | late ) ;
2022-03-15 07:34:13 +00:00
}
}
2022-01-04 01:47:26 +00:00
} ;
2022-10-29 08:18:19 +00:00
const Blend = struct {
const Self = @This ( ) ;
cnt : io . BldCnt ,
alpha : io . BldAlpha ,
y : io . BldY ,
pub fn create ( ) Self {
return . {
. cnt = . { . raw = 0x000 } ,
. alpha = . { . raw = 0x000 } ,
. y = . { . raw = 0x000 } ,
} ;
}
pub fn getCnt ( self : * const Self ) u16 {
return self . cnt . raw & 0x3FFF ;
}
pub fn getAlpha ( self : * const Self ) u16 {
return self . alpha . raw & 0x1F1F ;
}
} ;
2022-06-29 17:53:35 +00:00
const Window = struct {
const Self = @This ( ) ;
h : [ 2 ] io . WinH ,
v : [ 2 ] io . WinV ,
2022-10-30 05:15:26 +00:00
out : io . WinOut ,
in : io . WinIn ,
2022-06-29 17:53:35 +00:00
fn init ( ) Self {
return . {
. h = [ _ ] io . WinH { . { . raw = 0 } } * * 2 ,
. v = [ _ ] io . WinV { . { . raw = 0 } } * * 2 ,
2022-10-30 05:15:26 +00:00
. out = . { . raw = 0 } ,
. in = . { . raw = 0 } ,
2022-06-29 17:53:35 +00:00
} ;
}
2022-10-30 05:15:26 +00:00
pub fn getIn ( self : * const Self ) u16 {
return self . in . raw & 0x3F3F ;
2022-10-29 08:18:19 +00:00
}
2022-10-30 05:15:26 +00:00
pub fn getOut ( self : * const Self ) u16 {
return self . out . raw & 0x3F3F ;
2022-10-29 08:18:19 +00:00
}
2022-07-19 23:43:21 +00:00
fn inRange ( self : * const Self , comptime id : u1 , x : u9 , y : u8 ) bool {
2022-11-03 06:34:07 +00:00
const winh = self . h [ id ] ;
const winv = self . v [ id ] ;
2022-07-19 23:43:21 +00:00
2022-11-03 06:34:07 +00:00
if ( isYInRange ( winv , y ) ) {
const x1 = winh . x1 . read ( ) ;
const x2 = winh . x2 . read ( ) ;
2022-07-19 23:43:21 +00:00
// Within X Bounds
2022-11-03 06:34:07 +00:00
return if ( x1 < x2 ) blk : {
break : blk x > = x1 and x < x2 ;
} else blk : {
break : blk x > = x1 or x < x2 ;
} ;
2022-07-19 23:43:21 +00:00
}
return false ;
}
2022-11-03 06:34:07 +00:00
inline fn isYInRange ( winv : io . WinV , y : u9 ) bool {
const y1 = winv . y1 . read ( ) ;
const y2 = winv . y2 . read ( ) ;
if ( y1 < y2 ) {
return y > = y1 and y < y2 ;
} else {
return y > = y1 or y < y2 ;
}
}
2022-06-29 17:53:35 +00:00
pub fn setH ( self : * Self , value : u32 ) void {
self . h [ 0 ] . raw = @truncate ( u16 , value ) ;
self . h [ 1 ] . raw = @truncate ( u16 , value > > 16 ) ;
}
pub fn setV ( self : * Self , value : u32 ) void {
self . v [ 0 ] . raw = @truncate ( u16 , value ) ;
self . v [ 1 ] . raw = @truncate ( u16 , value > > 16 ) ;
}
pub fn setIo ( self : * Self , value : u32 ) void {
2022-10-30 05:15:26 +00:00
self . in . raw = @truncate ( u16 , value ) ;
self . out . raw = @truncate ( u16 , value > > 16 ) ;
2022-06-29 17:53:35 +00:00
}
} ;
2022-02-13 08:27:20 +00:00
const Background = struct {
const Self = @This ( ) ;
/// Read / Write
cnt : io . BackgroundControl ,
/// Write Only
hofs : io . BackgroundOffset ,
/// Write Only
vofs : io . BackgroundOffset ,
fn init ( ) Self {
return . {
. cnt = . { . raw = 0x0000 } ,
. hofs = . { . raw = 0x0000 } ,
. vofs = . { . raw = 0x0000 } ,
} ;
}
2022-10-29 08:18:19 +00:00
/// For whatever reason, some higher bits of BG0CNT
/// are masked out
pub inline fn bg0Cnt ( self : * const Self ) u16 {
return self . cnt . raw & 0xDFFF ;
}
/// BG1CNT inherits the same mask as BG0CNTs
pub inline fn bg1Cnt ( self : * const Self ) u16 {
return self . bg0Cnt ( ) ;
}
2022-02-13 08:27:20 +00:00
} ;
2022-02-16 05:09:06 +00:00
2022-05-18 18:50:40 +00:00
const AffineBackground = struct {
const Self = @This ( ) ;
2022-06-03 22:26:52 +00:00
x : i32 ,
y : i32 ,
2022-05-18 18:50:40 +00:00
2022-06-03 22:26:52 +00:00
pa : i16 ,
pb : i16 ,
pc : i16 ,
pd : i16 ,
x_latch : ? i32 ,
y_latch : ? i32 ,
2022-05-18 18:50:40 +00:00
fn init ( ) Self {
return . {
2022-06-03 22:26:52 +00:00
. x = 0 ,
. y = 0 ,
. pa = 0 ,
. pb = 0 ,
. pc = 0 ,
. pd = 0 ,
. x_latch = null ,
. y_latch = null ,
2022-05-18 18:50:40 +00:00
} ;
}
2022-06-15 04:33:36 +00:00
pub fn setX ( self : * Self , is_vblank : bool , value : u32 ) void {
self . x = @bitCast ( i32 , value ) ;
if ( ! is_vblank ) self . x_latch = @bitCast ( i32 , value ) ;
}
pub fn setY ( self : * Self , is_vblank : bool , value : u32 ) void {
self . y = @bitCast ( i32 , value ) ;
if ( ! is_vblank ) self . y_latch = @bitCast ( i32 , value ) ;
}
2022-05-18 18:50:40 +00:00
pub fn writePaPb ( self : * Self , value : u32 ) void {
2022-06-03 22:26:52 +00:00
self . pa = @bitCast ( i16 , @truncate ( u16 , value ) ) ;
self . pb = @bitCast ( i16 , @truncate ( u16 , value > > 16 ) ) ;
2022-05-18 18:50:40 +00:00
}
pub fn writePcPd ( self : * Self , value : u32 ) void {
2022-06-03 22:26:52 +00:00
self . pc = @bitCast ( i16 , @truncate ( u16 , value ) ) ;
self . pd = @bitCast ( i16 , @truncate ( u16 , value > > 16 ) ) ;
}
// Every Vblank BG?X/Y registers are latched
fn latchRefPoints ( self : * Self ) void {
self . x_latch = self . x ;
self . y_latch = self . y ;
2022-05-18 18:50:40 +00:00
}
} ;
2022-02-16 05:09:06 +00:00
const ScreenEntry = extern union {
tile_id : Bitfield ( u16 , 0 , 10 ) ,
h_flip : Bit ( u16 , 10 ) ,
v_flip : Bit ( u16 , 11 ) ,
2022-03-12 07:46:41 +00:00
pal_bank : Bitfield ( u16 , 12 , 4 ) ,
2022-02-16 05:09:06 +00:00
raw : u16 ,
} ;
2022-03-03 05:36:40 +00:00
2022-03-10 22:48:44 +00:00
const Sprite = struct {
const Self = @This ( ) ;
attr0 : Attr0 ,
attr1 : Attr1 ,
attr2 : Attr2 ,
2022-03-12 07:46:41 +00:00
width : u8 ,
height : u8 ,
2022-03-10 22:48:44 +00:00
fn init ( attr0 : Attr0 , attr1 : Attr1 , attr2 : Attr2 ) Self {
const d = spriteDimensions ( attr0 . shape . read ( ) , attr1 . size . read ( ) ) ;
return . {
. attr0 = attr0 ,
. attr1 = attr1 ,
. attr2 = attr2 ,
. width = d [ 0 ] ,
. height = d [ 1 ] ,
} ;
}
2022-04-21 13:15:52 +00:00
fn x ( self : * const Self ) u9 {
2022-03-10 22:48:44 +00:00
return self . attr1 . x . read ( ) ;
}
2022-04-21 13:15:52 +00:00
fn y ( self : * const Self ) u8 {
2022-03-10 22:48:44 +00:00
return self . attr0 . y . read ( ) ;
}
2022-06-04 22:00:47 +00:00
fn is8bpp ( self : * const Self ) bool {
2022-03-10 22:48:44 +00:00
return self . attr0 . is_8bpp . read ( ) ;
}
2022-06-04 22:00:47 +00:00
fn tileId ( self : * const Self ) u10 {
2022-03-10 22:48:44 +00:00
return self . attr2 . tile_id . read ( ) ;
}
2022-06-04 22:00:47 +00:00
fn palBank ( self : * const Self ) u4 {
2022-03-10 22:48:44 +00:00
return self . attr2 . pal_bank . read ( ) ;
}
2022-06-04 22:00:47 +00:00
fn hFlip ( self : * const Self ) bool {
2022-03-10 22:48:44 +00:00
return self . attr1 . h_flip . read ( ) ;
}
2022-06-04 22:00:47 +00:00
fn vFlip ( self : * const Self ) bool {
2022-03-10 22:48:44 +00:00
return self . attr1 . v_flip . read ( ) ;
}
2022-04-21 13:15:52 +00:00
fn priority ( self : * const Self ) u2 {
2022-03-10 22:48:44 +00:00
return self . attr2 . rel_prio . read ( ) ;
}
} ;
2022-05-01 22:15:56 +00:00
const AffineSprite = struct {
const Self = @This ( ) ;
attr0 : AffineAttr0 ,
attr1 : AffineAttr1 ,
attr2 : Attr2 ,
width : u8 ,
height : u8 ,
2022-06-04 22:00:47 +00:00
fn from ( sprite : Sprite ) AffineSprite {
2022-05-01 22:15:56 +00:00
return . {
2022-06-04 22:00:47 +00:00
. attr0 = . { . raw = sprite . attr0 . raw } ,
. attr1 = . { . raw = sprite . attr1 . raw } ,
. attr2 = sprite . attr2 ,
. width = sprite . width ,
. height = sprite . height ,
2022-05-01 22:15:56 +00:00
} ;
}
2022-06-04 22:00:47 +00:00
fn x ( self : * const Self ) u9 {
return self . attr1 . x . read ( ) ;
}
fn y ( self : * const Self ) u8 {
return self . attr0 . y . read ( ) ;
}
fn is8bpp ( self : * const Self ) bool {
return self . attr0 . is_8bpp . read ( ) ;
}
fn tileId ( self : * const Self ) u10 {
return self . attr2 . tile_id . read ( ) ;
}
fn palBank ( self : * const Self ) u4 {
return self . attr2 . pal_bank . read ( ) ;
}
fn matrixId ( self : * const Self ) u5 {
return self . attr1 . aff_sel . read ( ) ;
}
2022-05-01 22:15:56 +00:00
} ;
2022-03-03 05:36:40 +00:00
const Attr0 = extern union {
y : Bitfield ( u16 , 0 , 8 ) ,
2022-05-01 22:15:56 +00:00
is_affine : Bit ( u16 , 8 ) , // This SBZ
2022-03-03 05:36:40 +00:00
disabled : Bit ( u16 , 9 ) ,
mode : Bitfield ( u16 , 10 , 2 ) ,
mosaic : Bit ( u16 , 12 ) ,
is_8bpp : Bit ( u16 , 13 ) ,
2022-03-10 22:48:44 +00:00
shape : Bitfield ( u16 , 14 , 2 ) ,
2022-03-03 05:36:40 +00:00
raw : u16 ,
} ;
2022-05-01 22:15:56 +00:00
const AffineAttr0 = extern union {
y : Bitfield ( u16 , 0 , 8 ) ,
rot_scaling : Bit ( u16 , 8 ) , // This SB1
double_size : Bit ( u16 , 9 ) ,
mode : Bitfield ( u16 , 10 , 2 ) ,
mosaic : Bit ( u16 , 12 ) ,
is_8bpp : Bit ( u16 , 13 ) ,
shape : Bitfield ( u16 , 14 , 2 ) ,
raw : u16 ,
} ;
2022-03-03 05:36:40 +00:00
const Attr1 = extern union {
x : Bitfield ( u16 , 0 , 9 ) ,
h_flip : Bit ( u16 , 12 ) ,
v_flip : Bit ( u16 , 13 ) ,
size : Bitfield ( u16 , 14 , 2 ) ,
raw : u16 ,
} ;
2022-05-01 22:15:56 +00:00
const AffineAttr1 = extern union {
x : Bitfield ( u16 , 0 , 9 ) ,
aff_sel : Bitfield ( u16 , 9 , 5 ) ,
size : Bitfield ( u16 , 14 , 2 ) ,
raw : u16 ,
} ;
2022-03-03 05:36:40 +00:00
const Attr2 = extern union {
tile_id : Bitfield ( u16 , 0 , 10 ) ,
rel_prio : Bitfield ( u16 , 10 , 2 ) ,
2022-03-10 22:48:44 +00:00
pal_bank : Bitfield ( u16 , 12 , 4 ) ,
2022-06-04 22:00:47 +00:00
raw : u16 ,
2022-03-03 05:36:40 +00:00
} ;
2022-03-10 22:48:44 +00:00
2022-03-12 07:46:41 +00:00
fn spriteDimensions ( shape : u2 , size : u2 ) [ 2 ] u8 {
2022-03-10 22:48:44 +00:00
@setRuntimeSafety ( false ) ;
return switch ( shape ) {
0b00 = > switch ( size ) {
// Square
2022-03-12 07:46:41 +00:00
0b00 = > [ _ ] u8 { 8 , 8 } ,
0b01 = > [ _ ] u8 { 16 , 16 } ,
0b10 = > [ _ ] u8 { 32 , 32 } ,
0b11 = > [ _ ] u8 { 64 , 64 } ,
2022-03-10 22:48:44 +00:00
} ,
0b01 = > switch ( size ) {
2022-03-12 07:46:41 +00:00
0b00 = > [ _ ] u8 { 16 , 8 } ,
0b01 = > [ _ ] u8 { 32 , 8 } ,
0b10 = > [ _ ] u8 { 32 , 16 } ,
0b11 = > [ _ ] u8 { 64 , 32 } ,
2022-03-10 22:48:44 +00:00
} ,
0b10 = > switch ( size ) {
2022-03-12 07:46:41 +00:00
0b00 = > [ _ ] u8 { 8 , 16 } ,
0b01 = > [ _ ] u8 { 8 , 32 } ,
0b10 = > [ _ ] u8 { 16 , 32 } ,
0b11 = > [ _ ] u8 { 32 , 64 } ,
2022-03-10 22:48:44 +00:00
} ,
else = > std . debug . panic ( " {} is an invalid sprite shape " , . { shape } ) ,
} ;
}
2022-03-18 09:27:37 +00:00
2022-10-09 18:59:57 +00:00
inline fn rgba888 ( bgr555 : u16 ) u32 {
2022-03-18 09:27:37 +00:00
const b = @as ( u32 , bgr555 > > 10 & 0x1F ) ;
const g = @as ( u32 , bgr555 > > 5 & 0x1F ) ;
const r = @as ( u32 , bgr555 & 0x1F ) ;
return ( r < < 3 | r > > 2 ) < < 24 | ( g < < 3 | g > > 2 ) < < 16 | ( b < < 3 | b > > 2 ) < < 8 | 0xFF ;
}
2022-06-20 01:10:56 +00:00
fn alphaBlend ( top : u16 , btm : u16 , bldalpha : io . BldAlpha ) u16 {
const eva : u16 = bldalpha . eva . read ( ) ;
const evb : u16 = bldalpha . evb . read ( ) ;
const top_r = top & 0x1F ;
const top_g = ( top > > 5 ) & 0x1F ;
const top_b = ( top > > 10 ) & 0x1F ;
const btm_r = btm & 0x1F ;
const btm_g = ( btm > > 5 ) & 0x1F ;
const btm_b = ( btm > > 10 ) & 0x1F ;
const bld_r = std . math . min ( 31 , ( top_r * eva + btm_r * evb ) > > 4 ) ;
const bld_g = std . math . min ( 31 , ( top_g * eva + btm_g * evb ) > > 4 ) ;
const bld_b = std . math . min ( 31 , ( top_b * eva + btm_b * evb ) > > 4 ) ;
return ( bld_b < < 10 ) | ( bld_g < < 5 ) | bld_r ;
}
2022-06-23 05:31:34 +00:00
fn shouldDrawSprite ( bldcnt : io . BldCnt , scanline : * Scanline , x : u9 ) bool {
2022-11-17 16:16:35 +00:00
if ( scanline . top ( ) [ x ] = = . set ) return false ;
switch ( bldcnt . mode . read ( ) ) {
0b00 = > if ( scanline . top ( ) [ x ] = = . set ) return false , // pass through
0b01 = > {
// BLD_ALPHA
2022-12-17 13:53:40 +00:00
// We want to check if we're concerned aout the bottom layer first
2022-11-17 16:16:35 +00:00
// because if so, the top layer already having a pixel is OK
const btm_layers = bldcnt . layer_b . read ( ) ;
const is_btm_layer = ( btm_layers > > 4 ) & 1 = = 1 ;
2022-06-23 05:31:34 +00:00
2022-11-17 16:16:35 +00:00
if ( is_btm_layer and scanline . btm ( ) [ x ] = = . set ) return false ;
2022-06-23 05:31:34 +00:00
2022-11-17 16:16:35 +00:00
if ( scanline . top ( ) [ x ] = = . set ) return false ;
} ,
0b10 , 0b11 = > {
if ( scanline . top ( ) [ x ] = = . set ) return false ;
if ( scanline . btm ( ) [ x ] = = . set ) return false ;
} ,
2022-06-23 05:31:34 +00:00
}
return true ;
}
2022-11-17 16:16:35 +00:00
fn drawSpritePixel ( bldcnt : io . BldCnt , scanline : * Scanline , x : u9 , bgr555 : u16 ) void {
switch ( bldcnt . mode . read ( ) ) {
0b00 = > { } , // pass through
0b01 = > {
// BLD_ALPHA
const top_layers = bldcnt . layer_a . read ( ) ;
const is_top_layer = ( top_layers > > 4 ) & 1 = = 1 ;
2022-06-23 05:31:34 +00:00
2022-11-17 16:16:35 +00:00
if ( is_top_layer ) {
scanline . top ( ) [ x ] = Scanline . Pixel . from ( bgr555 ) ;
return ;
}
const btm_layers = bldcnt . layer_b . read ( ) ;
const is_btm_layer = ( btm_layers > > 4 ) & 1 = = 1 ;
if ( is_btm_layer ) {
scanline . btm ( ) [ x ] = Scanline . Pixel . from ( bgr555 ) ;
return ;
}
// We're rendering a normal pixel that isn't alpha blended
// we can mark the pixel on the bottom layer as hidden
scanline . btm ( ) [ x ] = . hidden ;
} ,
0b10 , 0b11 = > {
// This is explained in drawBackgroundPixel, we're reusing the bottom layer to draw layer A pixels we will want to
// later blend with WHITE or BLACK
const top_layers = bldcnt . layer_a . read ( ) ;
const is_top_layer = ( top_layers > > 4 ) & 1 = = 1 ;
if ( is_top_layer ) {
scanline . btm ( ) [ x ] = Scanline . Pixel . from ( bgr555 ) ; // This is intentional
return ;
}
} ,
2022-06-23 05:31:34 +00:00
}
2022-11-17 16:16:35 +00:00
scanline . top ( ) [ x ] = Scanline . Pixel . from ( bgr555 ) ;
2022-06-23 05:31:34 +00:00
}
2022-06-20 01:10:56 +00:00
const Scanline = struct {
const Self = @This ( ) ;
2022-11-17 16:16:35 +00:00
const Pixel = union ( enum ) {
set : u16 ,
unset : void ,
hidden : void ,
fn from ( bgr555 : u16 ) Pixel {
return . { . set = bgr555 } ;
}
} ;
layers : [ 2 ] [ ] Pixel ,
buf : [ ] Pixel ,
2022-06-20 01:10:56 +00:00
2022-08-29 05:32:41 +00:00
allocator : Allocator ,
2022-06-20 01:10:56 +00:00
2022-08-29 05:32:41 +00:00
fn init ( allocator : Allocator ) ! Self {
2022-11-17 16:16:35 +00:00
const buf = try allocator . alloc ( Pixel , width * 2 ) ; // Top & Bottom Scanline
std . mem . set ( Pixel , buf , . unset ) ;
2022-06-20 01:10:56 +00:00
return . {
2022-08-29 05:32:41 +00:00
// Top & Bototm Layers
2022-11-17 16:16:35 +00:00
. layers = [ _ ] [ ] Pixel { buf [ 0 . . ] [ 0 . . width ] , buf [ width . . ] [ 0 . . width ] } ,
2022-08-29 05:32:41 +00:00
. buf = buf ,
. allocator = allocator ,
2022-06-20 01:10:56 +00:00
} ;
}
fn reset ( self : * Self ) void {
2022-11-17 16:16:35 +00:00
std . mem . set ( Pixel , self . buf , . unset ) ;
2022-06-20 01:10:56 +00:00
}
2022-08-29 05:32:41 +00:00
fn deinit ( self : * Self ) void {
self . allocator . free ( self . buf ) ;
self . * = undefined ;
2022-06-20 01:10:56 +00:00
}
2022-11-17 16:16:35 +00:00
fn top ( self : * Self ) [ ] Pixel {
2022-08-29 05:32:41 +00:00
return self . layers [ 0 ] ;
2022-06-20 01:10:56 +00:00
}
2022-11-17 16:16:35 +00:00
fn btm ( self : * Self ) [ ] Pixel {
2022-08-29 05:32:41 +00:00
return self . layers [ 1 ] ;
2022-06-20 01:10:56 +00:00
}
} ;