624 lines
23 KiB
Go
624 lines
23 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"image"
|
|
)
|
|
|
|
/*
|
|
* This file is part of CD+Graphics Magic.
|
|
*
|
|
* CD+Graphics Magic is free software: you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License as
|
|
* published by the Free Software Foundation, either version 2 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* CD+Graphics Magic 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 General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with CD+Graphics Magic. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* This class instantiates an HTML5/Canvas CD+Graphics decoder object.
|
|
*
|
|
* This is the "low resource" version, and should be very close
|
|
* to as fast as possible with JavaScript.
|
|
*
|
|
* The difference between the "low resource" and normal version
|
|
* is that this one packs each 6 pixel font line in to one
|
|
* array value, unrolling some loops and minimizing array lookups.
|
|
*
|
|
* The only concession made is lack of H/V "offset" support used
|
|
* for smooth scrolling.
|
|
* Block based scrolls *are* still supported, however, so the basic
|
|
* intent of the graphics is presented, but less than ideally.
|
|
*
|
|
* It is recommended for CPU constrained (eg. mobile or embedded) devices.
|
|
*
|
|
*/
|
|
|
|
// Useful enums for CD+Graphics...
|
|
|
|
const (
|
|
VRAM_SIZE = 300 * 216 // Total linear size of VRAM, in pixels.
|
|
VRAM_WIDTH = 300 // Width (or pitch) of VRAM, in pixels.
|
|
VRAM_HEIGHT = 216 // Height of VRAM, in pixels.
|
|
VISIBLE_SIZE = 288 * 192 // Total linear size of visible screen, in pixels.
|
|
VISIBLE_WIDTH = 288 // Width (or pitch) of visible screen, in pixels.
|
|
VISIBLE_HEIGHT = 192 // Height of visible screen, in pixels.
|
|
FONT_WIDTH = 6 // Width of one "font" (or block).
|
|
FONT_HEIGHT = 12 // Height of one "font" (or block).
|
|
NUM_X_FONTS = 50 // Number of horizontal fonts contained in VRAM.
|
|
NUM_Y_FONTS = 18 // Number of vertical fonts contained in VRAM.
|
|
PALETTE_ENTRIES = 16 // Number of CLUT palette entries.
|
|
TV_GRAPHICS = 0x09 // 50x18 (48x16) 16 color TV graphics mode.
|
|
MEMORY_PRESET = 0x01 // Set all VRAM to palette index.
|
|
BORDER_PRESET = 0x02 // Set border to palette index.
|
|
LOAD_CLUT_LO = 0x1E // Load Color Look Up Table index 0 through 7.
|
|
LOAD_CLUT_HI = 0x1F // Load Color Look Up Table index 8 through 15.
|
|
COPY_FONT = 0x06 // Copy 12x6 pixel font to screen.
|
|
XOR_FONT = 0x26 // XOR 12x6 pixel font with existing VRAM values.
|
|
SCROLL_PRESET = 0x14 // Update scroll offset, copying if 0x20 or 0x10.
|
|
SCROLL_COPY = 0x18 // Update scroll offset, setting color if 0x20 or 0x10.
|
|
)
|
|
|
|
var (
|
|
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 = make([]uint8, 0)
|
|
internal_usedirtyrect = true
|
|
|
|
internal_border_index = byte(0x00) // The current border palette index.
|
|
internal_current_pack = byte(0x00)
|
|
|
|
internal_border_dirty = false
|
|
internal_screen_dirty = false
|
|
)
|
|
|
|
func init() {
|
|
internal_rgba_imagedata = internal_rgba_context.Pix
|
|
}
|
|
|
|
func main() {
|
|
|
|
fmt.Println("Compiles baby!")
|
|
}
|
|
|
|
func resetCDGState() {
|
|
internal_current_pack = 0x00
|
|
internal_border_index = 0x00
|
|
clearPalette()
|
|
clearVRAM(0x00)
|
|
clearDirtyBlocks()
|
|
}
|
|
|
|
func clearPalette() {
|
|
for idx := 0; idx < PALETTE_ENTRIES; idx++ {
|
|
internal_palette[idx] = 0x00
|
|
}
|
|
}
|
|
|
|
func get_current_pack() byte {
|
|
//casting: must test!!!
|
|
return byte(internal_current_pack)
|
|
}
|
|
|
|
/* Possibly not needed!
|
|
func set_dirtyrect(requested_value) {
|
|
internal_usedirtyrect = requested_value
|
|
}
|
|
*/
|
|
|
|
//Not sure I need this function
|
|
func putImageData(imageData []byte, x, y, dirtyX, dirtyY, dirtyWidth, dirtyHeight int) {
|
|
|
|
}
|
|
|
|
func redrawCanvas() {
|
|
|
|
if internal_border_dirty || internal_screen_dirty {
|
|
// render_screen_to_rgb()
|
|
internal_screen_dirty = false
|
|
clearDirtyBlocks()
|
|
// internal_rgba_context.putImageData(internal_rgba_imagedata, 0, 0)
|
|
} else {
|
|
//var local_context = internal_rgba_context
|
|
//var local_rgba_imagedata = internal_rgba_imagedata
|
|
|
|
update_needed := false
|
|
var blk = 0x00
|
|
|
|
//NOTE: test the post-increment (Go does not have pre, so had to change it)
|
|
|
|
for y_blk := 1; y_blk <= 16; y_blk++ {
|
|
|
|
blk = y_blk*NUM_X_FONTS + 1
|
|
|
|
for x_blk := 1; x_blk <= 48; x_blk++ {
|
|
|
|
if internal_dirty_blocks[blk] != 0 {
|
|
|
|
render_block_to_rgb(x_blk, y_blk)
|
|
|
|
if internal_usedirtyrect {
|
|
//api call looks like this
|
|
//context.putImageData(imgData,x,y,dirtyX,dirtyY,dirtyWidth,dirtyHeight);
|
|
// 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 = true
|
|
}
|
|
|
|
internal_dirty_blocks[blk] = 0x00
|
|
}
|
|
//Note: test the post-increment
|
|
blk++
|
|
}
|
|
}
|
|
// Update the whole screen for browsers where dirty rect isn't supported.
|
|
// Since this can't be detected(???) in any way, it has to be User Agent selected, or an actual user option.
|
|
// TODO: See if a dirty rect-based partial update of known pixel values combined with a getImageData
|
|
// call could be used to determine if it works correctly *without* evil browser sniffing.
|
|
if update_needed {
|
|
//local_context.putImageData(local_rgba_imagedata, 0, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Decode to pack playback_position, using cdg_file_data.
|
|
func decode_packs(cdg_file_data []byte, playback_position byte) {
|
|
|
|
for curr_pack := internal_current_pack; curr_pack < playback_position; curr_pack++ {
|
|
|
|
start_offset := curr_pack * 24
|
|
curr_command := cdg_file_data[start_offset] & 0x3F
|
|
|
|
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)
|
|
|
|
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 {
|
|
|
|
adjusted_value := requested_index // Pixel 0
|
|
adjusted_value |= (requested_index << 004) // Pixel 1
|
|
adjusted_value |= (requested_index << 010) // Pixel 2
|
|
adjusted_value |= (requested_index << 014) // Pixel 3
|
|
adjusted_value |= (requested_index << 020) // Pixel 4
|
|
adjusted_value |= (requested_index << 024) // Pixel 5
|
|
|
|
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 := byte(0x00) // RGBA value of current pixel.
|
|
curr_line_indices := byte(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 := byte(0x00) // RGBA value of current pixel.
|
|
curr_line_indices := byte(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 := byte(pal_inc) + pal_offset
|
|
temp_rgb := byte(0x00000000)
|
|
temp_entry := byte(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 != internal_palette[temp_idx] {
|
|
internal_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) != 0 {
|
|
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 := int(y_location)*600 + int(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 := byte(0x00) // Subcode byte for current pixel row.
|
|
temp_pxl := byte(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 := byte(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.
|
|
}
|
|
|
|
func proc_VRAM_HSCROLL(direction, copy_flag, color byte) {
|
|
|
|
buf := byte(0)
|
|
line_color := fill_line_with_palette_index(color)
|
|
|
|
if direction == 0x02 {
|
|
// Step through the lines one at a time...
|
|
for y_src := 0; y_src < (50 * 216); y_src += 50 {
|
|
y_start := y_src
|
|
buf = internal_vram[y_start]
|
|
|
|
for x_src := y_start + 1; x_src < y_start+50; x_src++ {
|
|
internal_vram[x_src-1] = internal_vram[x_src]
|
|
}
|
|
|
|
if copy_flag != 0 {
|
|
internal_vram[y_start+49] = buf
|
|
} else {
|
|
internal_vram[y_start+49] = line_color
|
|
}
|
|
}
|
|
} else if direction == 0x01 {
|
|
// Step through the lines on at a time.
|
|
for y_src := 0; y_src < (50 * 216); y_src += 50 {
|
|
// Copy the last six lines to the buffer.
|
|
y_start := y_src
|
|
buf = internal_vram[y_start+49]
|
|
|
|
for x_src := y_start + 48; x_src >= y_start; x_src-- {
|
|
internal_vram[x_src+1] = internal_vram[x_src]
|
|
}
|
|
|
|
if copy_flag != 0 {
|
|
internal_vram[y_start] = buf
|
|
} else {
|
|
internal_vram[y_start] = line_color
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func proc_VRAM_VSCROLL(direction, copy_flag, color byte) {
|
|
|
|
offscreen_size := NUM_X_FONTS * FONT_HEIGHT
|
|
buf := make([]byte, offscreen_size)
|
|
|
|
line_color := fill_line_with_palette_index(color)
|
|
|
|
if direction == 0x02 {
|
|
dst_idx := 0 // Buffer destination starts at 0.
|
|
// Copy the top 300x12 pixels into the buffer.
|
|
for src_idx := 0; src_idx < offscreen_size; src_idx++ {
|
|
buf[dst_idx] = internal_vram[src_idx]
|
|
dst_idx++
|
|
}
|
|
|
|
dst_idx = 0 // Destination starts at the first line.
|
|
|
|
for src_idx := offscreen_size; src_idx < (50 * 216); src_idx++ {
|
|
internal_vram[dst_idx] = internal_vram[src_idx]
|
|
dst_idx++
|
|
}
|
|
|
|
dst_idx = NUM_X_FONTS * 204 // Destination begins at line 204.
|
|
|
|
if copy_flag != 0 {
|
|
for src_idx := 0; src_idx < offscreen_size; src_idx++ {
|
|
internal_vram[dst_idx] = buf[src_idx]
|
|
dst_idx++
|
|
}
|
|
} else {
|
|
for src_idx := 0; src_idx < offscreen_size; src_idx++ {
|
|
internal_vram[dst_idx] = line_color
|
|
dst_idx++
|
|
}
|
|
}
|
|
} else if direction == 0x01 {
|
|
dst_idx := 0 // Buffer destination starts at 0.
|
|
// Copy the bottom 300x12 pixels into the buffer.
|
|
for src_idx := (50 * 204); src_idx < (50 * 216); src_idx++ {
|
|
buf[dst_idx] = internal_vram[src_idx]
|
|
dst_idx++
|
|
}
|
|
|
|
for src_idx := (50 * 204) - 1; src_idx > 0; src_idx-- {
|
|
internal_vram[src_idx+offscreen_size] = internal_vram[src_idx]
|
|
}
|
|
|
|
if copy_flag != 0 {
|
|
for src_idx := 0; src_idx < offscreen_size; src_idx++ {
|
|
internal_vram[src_idx] = buf[src_idx]
|
|
}
|
|
} else {
|
|
for src_idx := 0; src_idx < offscreen_size; src_idx++ {
|
|
internal_vram[src_idx] = line_color
|
|
}
|
|
}
|
|
}
|
|
}
|