diff --git a/HTML5-CDGPlayer_files/cdgmagic_cdgdecoder_lowresource.go b/HTML5-CDGPlayer_files/cdgmagic_cdgdecoder_lowresource.go index 6342d1d..5c92031 100644 --- a/HTML5-CDGPlayer_files/cdgmagic_cdgdecoder_lowresource.go +++ b/HTML5-CDGPlayer_files/cdgmagic_cdgdecoder_lowresource.go @@ -1,6 +1,9 @@ package main -import "fmt" +import ( + "fmt" + "image" +) /* * This file is part of CD+Graphics Magic. @@ -65,9 +68,12 @@ const ( ) var ( - internal_palette = make([]byte, PALETTE_ENTRIES) - internal_vram = make([]byte, NUM_X_FONTS*VRAM_HEIGHT) - internal_dirty_blocks = make([]byte, 900) + internal_palette = make([]byte, PALETTE_ENTRIES) + internal_vram = make([]byte, NUM_X_FONTS*VRAM_HEIGHT) + internal_dirty_blocks = make([]byte, 900) + internal_rgba_context = image.NewRGBA(image.Rect(0, 0, VISIBLE_WIDTH, VISIBLE_HEIGHT)) + internal_rgba_imagedata = internal_rgba_imagedata.Pix + internal_usedirtyrect = true internal_border_index = 0x00 // The current border palette index. internal_current_pack = 0x00 // @@ -109,8 +115,8 @@ func redrawCanvas() { if internal_border_dirty || internal_screen_dirty { // render_screen_to_rgb() - // internal_screen_dirty = 0 - // clear_dirty_blocks() + internal_screen_dirty = false + clearDirtyBlocks() // internal_rgba_context.putImageData(internal_rgba_imagedata, 0, 0) } else { var local_context = internal_rgba_context @@ -131,14 +137,14 @@ func redrawCanvas() { render_block_to_rgb(x_blk, y_blk) - if internal_usedirtyrect == 0x01 { + if internal_usedirtyrect { local_context.putImageData(local_rgba_imagedata, 0, 0, (x_blk-1)*FONT_WIDTH, (y_blk-1)*FONT_HEIGHT, FONT_WIDTH, FONT_HEIGHT) } else { - update_needed = 0x01 + update_needed = true } internal_dirty_blocks[blk] = 0x00 @@ -157,21 +163,43 @@ func redrawCanvas() { } } -func clearVRAM(colorIndex byte) { +// Decode to pack playback_position, using cdg_file_data. +func decode_packs(cdg_file_data []byte, playback_position int) { - packed_line_value := fill_line_with_palette_index(colorIndex) + for curr_pack := internal_current_pack; curr_pack < playback_position; curr_pack++ { - for pxl := 0; pxl < len(internal_vram); pxl++ { - internal_vram[pxl] = packed_line_value - } + start_offset := curr_pack * 24 + curr_command := cdg_file_data[start_offset] & 0x3F - internal_screen_dirty = true -} + if curr_command == TV_GRAPHICS { + // Slice the file array down to a single pack array. + this_pack := cdg_file_data[start_offset : start_offset+24] + // Pluck out the graphics instruction. + curr_instruction := this_pack[1] & 0x3F + // Perform the instruction action. + switch curr_instruction { + case MEMORY_PRESET: + proc_MEMORY_PRESET(this_pack) -func clearDirtyBlocks() { - for blk := 0; blk < 900; blk++ { - internal_dirty_blocks[blk] = 0x00 + case BORDER_PRESET: + proc_BORDER_PRESET(this_pack) + + case LOAD_CLUT_LO: + case LOAD_CLUT_HI: + proc_LOAD_CLUT(this_pack) + + case COPY_FONT: + case XOR_FONT: + proc_WRITE_FONT(this_pack) + + case SCROLL_PRESET: + case SCROLL_COPY: + proc_DO_SCROLL(this_pack) + + } + } } + internal_current_pack = playback_position } func fill_line_with_palette_index(requested_index byte) byte { @@ -185,3 +213,302 @@ func fill_line_with_palette_index(requested_index byte) byte { return adjusted_value } + +func clearDirtyBlocks() { + for blk := 0; blk < 900; blk++ { + internal_dirty_blocks[blk] = 0x00 + } +} + +func clearVRAM(colorIndex byte) { + + packed_line_value := fill_line_with_palette_index(colorIndex) + + for pxl := 0; pxl < len(internal_vram); pxl++ { + internal_vram[pxl] = packed_line_value + } + + internal_screen_dirty = true +} + +func render_screen_to_rgb() { + + vis_width := 48 + vis_height := VISIBLE_HEIGHT + + vram_loc := 601 // Offset into VRAM array. + rgb_loc := 0x00 // Offset into RGBA array. + curr_rgb := 0x00 // RGBA value of current pixel. + curr_line_indices := 0x00 // Packed font row index values. + + for y_pxl := 0; y_pxl < vis_height; y_pxl++ { + for x_pxl := 0; x_pxl < vis_width; x_pxl++ { + + //for the Go version, maybe don't have to unroll the loop cause it's getting ugly. + //NOTE: these values are shifted by Octal numbers looks like ie: 010 + //NOTE: In Go, ++ is a statement not expression, so had to post-increment after-the-fact + + curr_line_indices = internal_vram[vram_loc] // Get the current line segment indices. + vram_loc++ + curr_rgb = internal_palette[(curr_line_indices>>000)&0x0F] // Get the RGB value for pixel 0. + internal_rgba_imagedata[rgb_loc] = (curr_rgb >> 020) & 0xFF // Set red value for pixel 0. + rgb_loc++ + internal_rgba_imagedata[rgb_loc] = (curr_rgb >> 010) & 0xFF // Set green value for pixel 0. + rgb_loc++ + internal_rgba_imagedata[rgb_loc] = (curr_rgb >> 000) & 0xFF // Set blue value for pixel 0. + rgb_loc++ + internal_rgba_imagedata[rgb_loc] = 0xFF + rgb_loc++ // Set alpha value (fully opaque) for pixel 0. + + curr_rgb = internal_palette[(curr_line_indices>>004)&0x0F] // Get the RGB value for pixel 1. + internal_rgba_imagedata[rgb_loc] = (curr_rgb >> 020) & 0xFF // Set red value for pixel 1. + rgb_loc++ + internal_rgba_imagedata[rgb_loc] = (curr_rgb >> 010) & 0xFF // Set green value for pixel 1. + rgb_loc++ + internal_rgba_imagedata[rgb_loc] = (curr_rgb >> 000) & 0xFF // Set blue value for pixel 1. + rgb_loc++ + internal_rgba_imagedata[rgb_loc] = 0xFF // Set alpha value (fully opaque) for pixel 1. + rgb_loc++ + curr_rgb = internal_palette[(curr_line_indices>>010)&0x0F] // Get the RGB value for pixel 2. + + internal_rgba_imagedata[rgb_loc] = (curr_rgb >> 020) & 0xFF // Set red value for pixel 2. + rgb_loc++ + internal_rgba_imagedata[rgb_loc] = (curr_rgb >> 010) & 0xFF // Set green value for pixel 2. + rgb_loc++ + internal_rgba_imagedata[rgb_loc] = (curr_rgb >> 000) & 0xFF // Set blue value for pixel 2. + rgb_loc++ + internal_rgba_imagedata[rgb_loc] = 0xFF + rgb_loc++ // Set alpha value (fully opaque) for pixel 2. + + curr_rgb = internal_palette[(curr_line_indices>>014)&0x0F] // Get the RGB value for pixel 3. + internal_rgba_imagedata[rgb_loc] = (curr_rgb >> 020) & 0xFF // Set red value for pixel 3. + rgb_loc++ + internal_rgba_imagedata[rgb_loc] = (curr_rgb >> 010) & 0xFF // Set green value for pixel 3. + rgb_loc++ + internal_rgba_imagedata[rgb_loc] = (curr_rgb >> 000) & 0xFF // Set blue value for pixel 3. + rgb_loc++ + internal_rgba_imagedata[rgb_loc] = 0xFF // Set alpha value (fully opaque) for pixel 3. + rgb_loc++ + + curr_rgb = internal_palette[(curr_line_indices>>020)&0x0F] // Get the RGB value for pixel 4. + internal_rgba_imagedata[rgb_loc] = (curr_rgb >> 020) & 0xFF // Set red value for pixel 4. + rgb_loc++ + internal_rgba_imagedata[rgb_loc] = (curr_rgb >> 010) & 0xFF // Set green value for pixel 4. + rgb_loc++ + internal_rgba_imagedata[rgb_loc] = (curr_rgb >> 000) & 0xFF // Set blue value for pixel 4. + rgb_loc++ + internal_rgba_imagedata[rgb_loc] = 0xFF + rgb_loc++ // Set alpha value (fully opaque) for pixel 4. + + curr_rgb = internal_palette[(curr_line_indices>>024)&0x0F] // Get the RGB value for pixel 5. + internal_rgba_imagedata[rgb_loc] = (curr_rgb >> 020) & 0xFF // Set red value for pixel 5. + rgb_loc++ + internal_rgba_imagedata[rgb_loc] = (curr_rgb >> 010) & 0xFF // Set green value for pixel 5. + rgb_loc++ + internal_rgba_imagedata[rgb_loc] = (curr_rgb >> 000) & 0xFF // Set blue value for pixel 5. + rgb_loc++ + internal_rgba_imagedata[rgb_loc] = 0xFF // Set alpha value (fully opaque) for pixel 5. + rgb_loc++ + // Or, instead, index 0 could be set transparent to show background image/video. + // Alternately, SET_TRANSPARENT instruction could be implemented to set 6bit transparency. + // Unfortunately, I don't think many (any?) discs bother to set it :-/... + } + vram_loc += 2 // Skip the offscreen font blocks. + } +} + +func render_block_to_rgb(x_start, y_start int) { + vram_loc := (y_start * NUM_X_FONTS * FONT_HEIGHT) + x_start // Offset into VRAM array. + vram_inc := NUM_X_FONTS + vram_end := vram_loc + (NUM_X_FONTS * FONT_HEIGHT) // VRAM location to end. + rgb_loc := (y_start - 1) * FONT_HEIGHT * VISIBLE_WIDTH // Row start. + rgb_loc += (x_start - 1) * FONT_WIDTH // Column start + rgb_loc *= 4 // RGBA, 1 pxl = 4 bytes. + + rgb_inc := (VISIBLE_WIDTH - FONT_WIDTH) * 4 + curr_rgb := 0x00 // RGBA value of current pixel. + curr_line_indices := 0x00 // Packed font row index values. + + for vram_loc < vram_end { + curr_line_indices = internal_vram[vram_loc] // Get the current line segment indices. + curr_rgb = internal_palette[(curr_line_indices>>000)&0x0F] // Get the RGB value for pixel 0. + internal_rgba_imagedata[rgb_loc] = (curr_rgb >> 020) & 0xFF // Set red value for pixel 0. + rgb_loc++ + internal_rgba_imagedata[rgb_loc] = (curr_rgb >> 010) & 0xFF // Set green value for pixel 0. + rgb_loc++ + internal_rgba_imagedata[rgb_loc] = (curr_rgb >> 000) & 0xFF // Set blue value for pixel 0. + rgb_loc++ + internal_rgba_imagedata[rgb_loc] = 0xFF + rgb_loc++ // Set alpha value (fully opaque) for pixel 0. + curr_rgb = internal_palette[(curr_line_indices>>004)&0x0F] // Get the RGB value for pixel 1. + internal_rgba_imagedata[rgb_loc] = (curr_rgb >> 020) & 0xFF // Set red value for pixel 1. + rgb_loc++ + internal_rgba_imagedata[rgb_loc] = (curr_rgb >> 010) & 0xFF // Set green value for pixel 1. + rgb_loc++ + internal_rgba_imagedata[rgb_loc] = (curr_rgb >> 000) & 0xFF // Set blue value for pixel 1. + rgb_loc++ + internal_rgba_imagedata[rgb_loc] = 0xFF + rgb_loc++ // Set alpha value (fully opaque) for pixel 1. + curr_rgb = internal_palette[(curr_line_indices>>010)&0x0F] // Get the RGB value for pixel 2. + internal_rgba_imagedata[rgb_loc] = (curr_rgb >> 020) & 0xFF // Set red value for pixel 2. + rgb_loc++ + internal_rgba_imagedata[rgb_loc] = (curr_rgb >> 010) & 0xFF // Set green value for pixel 2. + rgb_loc++ + internal_rgba_imagedata[rgb_loc] = (curr_rgb >> 000) & 0xFF // Set blue value for pixel 2. + rgb_loc++ + internal_rgba_imagedata[rgb_loc] = 0xFF + rgb_loc++ // Set alpha value (fully opaque) for pixel 2. + curr_rgb = internal_palette[(curr_line_indices>>014)&0x0F] // Get the RGB value for pixel 3. + internal_rgba_imagedata[rgb_loc] = (curr_rgb >> 020) & 0xFF // Set red value for pixel 3. + rgb_loc++ + internal_rgba_imagedata[rgb_loc] = (curr_rgb >> 010) & 0xFF // Set green value for pixel 3. + rgb_loc++ + internal_rgba_imagedata[rgb_loc] = (curr_rgb >> 000) & 0xFF // Set blue value for pixel 3. + rgb_loc++ + internal_rgba_imagedata[rgb_loc] = 0xFF + rgb_loc++ // Set alpha value (fully opaque) for pixel 3. + curr_rgb = internal_palette[(curr_line_indices>>020)&0x0F] // Get the RGB value for pixel 4. + internal_rgba_imagedata[rgb_loc] = (curr_rgb >> 020) & 0xFF // Set red value for pixel 4. + rgb_loc++ + internal_rgba_imagedata[rgb_loc] = (curr_rgb >> 010) & 0xFF // Set green value for pixel 4. + rgb_loc++ + internal_rgba_imagedata[rgb_loc] = (curr_rgb >> 000) & 0xFF // Set blue value for pixel 4. + rgb_loc++ + internal_rgba_imagedata[rgb_loc] = 0xFF + rgb_loc++ // Set alpha value (fully opaque) for pixel 4. + curr_rgb = internal_palette[(curr_line_indices>>024)&0x0F] // Get the RGB value for pixel 5. + internal_rgba_imagedata[rgb_loc] = (curr_rgb >> 020) & 0xFF // Set red value for pixel 5. + rgb_loc++ + internal_rgba_imagedata[rgb_loc] = (curr_rgb >> 010) & 0xFF // Set green value for pixel 5. + rgb_loc++ + internal_rgba_imagedata[rgb_loc] = (curr_rgb >> 000) & 0xFF // Set blue value for pixel 5. + rgb_loc++ + internal_rgba_imagedata[rgb_loc] = 0xFF // Set alpha value (fully opaque) for pixel 5. + rgb_loc++ + // Or, instead, index 0 could be set transparent to show background image/video. + // Alternately, SET_TRANSPARENT instruction could be implemented to set 6bit transparency. + // Unfortunately, I don't think many (any?) discs bother to set it :-/... + vram_loc += vram_inc // Move to the first column of the next row of this font block in VRAM. + rgb_loc += rgb_inc // Move to the first column of the next row of this font block in RGB pixels. + } +} + +//########## PRIVATE GRAPHICS DECODE FUNCTIONS ##########// + +func proc_BORDER_PRESET(cdg_pack []byte) { + // NOTE: The "border" is actually a DIV element, which can be very expensive to change in some browsers. + // This somewhat bizarre check ensures that the DIV is only touched if the actual RGB color is different, + // but the border index variable is always set... A similar check is also performed during palette update. + new_border_index := cdg_pack[4] & 0x3F // Get the border index from subcode. + // Check if the new border **RGB** color is different from the old one. + if internal_palette[new_border_index] != internal_palette[internal_border_index] { + internal_border_dirty = true // Border needs updating. + } + + internal_border_index = new_border_index // Set the new index. +} + +func proc_MEMORY_PRESET(cdg_pack []byte) { + clearVRAM(cdg_pack[4] & 0x3F) +} + +func proc_LOAD_CLUT(cdg_pack []byte) { + + // If instruction is 0x1E then 8*0=0, if 0x1F then 8*1=8 for offset. + pal_offset := (cdg_pack[1] & 0x01) * 8 + // Step through the eight color indices, setting the RGB values. + for pal_inc := 0; pal_inc < 8; pal_inc++ { + temp_idx := pal_inc + pal_offset + temp_rgb := 0x00000000 + temp_entry := 0x00000000 + // Set red. + temp_entry = (cdg_pack[pal_inc*2+4] & 0x3C) >> 2 + temp_rgb |= (temp_entry * 17) << 020 + // Set green. + temp_entry = ((cdg_pack[pal_inc*2+4] & 0x03) << 2) | ((cdg_pack[pal_inc*2+5] & 0x30) >> 4) + temp_rgb |= (temp_entry * 17) << 010 + // Set blue. + temp_entry = cdg_pack[pal_inc*2+5] & 0x0F + temp_rgb |= (temp_entry * 17) << 000 + + // Put the full RGB value into the index position, but only if it's different. + if temp_rgb != innternal_palette[temp_idx] { + innternal_palette[temp_idx] = temp_rgb + internal_screen_dirty = true // The colors are now different, so we need to update the whole screen. + + if temp_idx == internal_border_index { + internal_border_dirty = true + } // The border color has changed. + } + } +} + +func proc_WRITE_FONT(cdg_pack []byte) { + var local_vram = internal_vram + var local_dirty = internal_dirty_blocks + // Hacky hack to play channels 0 and 1 only... Ideally, there should be a function and user option to get/set. + active_channels := 0x03 + // First, get the channel... + subcode_channel := ((cdg_pack[4] & 0x30) >> 2) | ((cdg_pack[5] & 0x30) >> 4) + xor_var := cdg_pack[1] & 0x20 + // Then see if we should display it. + if (active_channels >> subcode_channel) & 0x01 { + x_location := cdg_pack[7] & 0x3F // Get horizontal font location. + y_location := cdg_pack[6] & 0x1F // Get vertical font location. + + // Verify we're not going to overrun the boundaries (i.e. bad data from a scratched disc). + if (x_location <= 49) && (y_location <= 17) { + start_pixel := y_location*600 + x_location // Location of first pixel of this font in linear VRAM. + // NOTE: Profiling indicates charCodeAt() uses ~80% of the CPU consumed for this function. + // Caching these values reduces that to a negligible amount. + + current_indexes = make([]byte, 2) + current_indexes[0] = (cdg_pack[4] & 0x0F) + current_indexes[1] = (cdg_pack[5] & 0x0F) + + current_row := 0x00 // Subcode byte for current pixel row. + temp_pxl := 0x00 // Decoded and packed 4bit pixel index values of current row. + for y_inc := 0; y_inc < 12; y_inc++ { + var pix_pos = y_inc*50 + start_pixel // Location of the first pixel of this row in linear VRAM. + current_row = cdg_pack[y_inc+8] // Get the subcode byte for the current row. + temp_pxl = (current_indexes[(current_row>>5)&0x01] << 000) + temp_pxl |= (current_indexes[(current_row>>4)&0x01] << 004) + temp_pxl |= (current_indexes[(current_row>>3)&0x01] << 010) + temp_pxl |= (current_indexes[(current_row>>2)&0x01] << 014) + temp_pxl |= (current_indexes[(current_row>>1)&0x01] << 020) + temp_pxl |= (current_indexes[(current_row>>0)&0x01] << 024) + + //NOTE: figure out truthy-ness of xor_var + if xor_var != 0 { + local_vram[pix_pos] ^= temp_pxl + } else { + local_vram[pix_pos] = temp_pxl + } + } // End of Y loop. + // Mark this block as needing an update. + local_dirty[y_location*50+x_location] = 0x01 + } // End of location check. + } // End of channel check. +} + +func proc_DO_SCROLL(cdg_pack []byte) { + direction := 0 // H/V direction flag. + copy_flag = (cdg_pack[1] & 0x08) >> 3 // Type of copy (memory preset or copy). + color = cdg_pack[4] & 0x0F // Color index to use for preset type. + + //TODOD: check what value of direction is + // Process horizontal commands. + if direction = (cdg_pack[5] & 0x30) >> 4; direction != 0 { + proc_VRAM_HSCROLL(direction, copy_flag, color) + } + + // Process vertical commands. + if direction = (cdg_pack[6] & 0x30) >> 4; direction != 0 { + proc_VRAM_VSCROLL(direction, copy_flag, color) + } + + internal_screen_dirty = true // Entire screen needs to be redrawn. +} + +//TODO: proc_VRAM_HSCROLL +//TODO: proc_VRAM_VSCROLL