diff --git a/src/gopheros/device/video/console/vesa_fb.go b/src/gopheros/device/video/console/vesa_fb.go index 9648d13..b5155e3 100644 --- a/src/gopheros/device/video/console/vesa_fb.go +++ b/src/gopheros/device/video/console/vesa_fb.go @@ -123,6 +123,8 @@ func (cons *VesaFbConsole) Fill(x, y, width, height uint32, _, bg uint8) { switch cons.bpp { case 8: cons.fill8(pX, pY, pW, pH, bg) + case 15, 16: + cons.fill16(pX, pY, pW, pH, bg) case 24, 32: cons.fill24(pX, pY, pW, pH, bg) } @@ -138,6 +140,18 @@ func (cons *VesaFbConsole) fill8(pX, pY, pW, pH uint32, bg uint8) { } } +// fill16 implements a fill operation using a 15/16bpp framebuffer. +func (cons *VesaFbConsole) fill16(pX, pY, pW, pH uint32, bg uint8) { + comp := cons.packColor16(bg) + fbRowOffset := cons.fbOffset(pX, pY) + for ; pH > 0; pH, fbRowOffset = pH-1, fbRowOffset+cons.pitch { + for fbOffset := fbRowOffset; fbOffset < fbRowOffset+pW*cons.bytesPerPixel; fbOffset += cons.bytesPerPixel { + cons.fb[fbOffset] = comp[0] + cons.fb[fbOffset+1] = comp[1] + } + } +} + // fill24 implements a fill operation using a 24/32bpp framebuffer. func (cons *VesaFbConsole) fill24(pX, pY, pW, pH uint32, bg uint8) { comp := cons.packColor24(bg) @@ -190,6 +204,8 @@ func (cons *VesaFbConsole) Write(ch byte, fg, bg uint8, x, y uint32) { switch cons.bpp { case 8: cons.write8(ch, fg, bg, pX, pY) + case 15, 16: + cons.write16(ch, fg, bg, pX, pY) case 24, 32: cons.write24(ch, fg, bg, pX, pY) } @@ -328,6 +344,23 @@ func (cons *VesaFbConsole) packColor24(colorIndex uint8) [3]uint8 { } } +// packColor16 encodes a palette color into the pixel format required by a +// 15/16 bpp framebuffer. +func (cons *VesaFbConsole) packColor16(colorIndex uint8) [2]uint8 { + var ( + c = cons.palette[colorIndex].(color.RGBA) + packed uint16 = 0 | + (uint16(c.R>>(8-cons.colorInfo.RedMaskSize)) << cons.colorInfo.RedPosition) | + (uint16(c.G>>(8-cons.colorInfo.GreenMaskSize)) << cons.colorInfo.GreenPosition) | + (uint16(c.B>>(8-cons.colorInfo.BlueMaskSize)) << cons.colorInfo.BluePosition) + ) + + return [2]uint8{ + uint8(packed), + uint8(packed >> 8), + } +} + // Palette returns the active color palette for this console. func (cons *VesaFbConsole) Palette() color.Palette { return cons.palette @@ -353,6 +386,12 @@ func (cons *VesaFbConsole) SetPaletteColor(index uint8, rgba color.RGBA) { portWriteByteFn(0x3c9, rgba.R>>2) portWriteByteFn(0x3c9, rgba.G>>2) portWriteByteFn(0x3c9, rgba.B>>2) + case 15, 16: + if oldColor == nil { + return + } + + cons.replace16(oldColor.(color.RGBA), rgba) case 24, 32: if oldColor == nil { return @@ -362,6 +401,24 @@ func (cons *VesaFbConsole) SetPaletteColor(index uint8, rgba color.RGBA) { } } +// replace16 replaces all srcColor values with dstColor using a 15/16bpp +// framebuffer. +func (cons *VesaFbConsole) replace16(src, dst color.RGBA) { + tmp := cons.palette[0] + cons.palette[0] = src + srcComp := cons.packColor16(0) + cons.palette[0] = dst + dstComp := cons.packColor16(0) + cons.palette[0] = tmp + for fbOffset := uint32(0); fbOffset < uint32(len(cons.fb)); fbOffset += cons.bytesPerPixel { + if cons.fb[fbOffset] == srcComp[0] && + cons.fb[fbOffset+1] == srcComp[1] { + cons.fb[fbOffset] = dstComp[0] + cons.fb[fbOffset+1] = dstComp[1] + } + } +} + // replace24 replaces all srcColor values with dstColor using a 24/32bpp // framebuffer. func (cons *VesaFbConsole) replace24(src, dst color.RGBA) { diff --git a/src/gopheros/device/video/console/vesa_fb_test.go b/src/gopheros/device/video/console/vesa_fb_test.go index ebbd8ba..d32af9c 100644 --- a/src/gopheros/device/video/console/vesa_fb_test.go +++ b/src/gopheros/device/video/console/vesa_fb_test.go @@ -148,6 +148,106 @@ func TestVesaFbWrite8bpp(t *testing.T) { } } +func TestVesaFbWrite16bpp(t *testing.T) { + specs := []struct { + consW, consH, offsetY uint32 + font *font.Font + expFb []byte + }{ + { + 16, 16, 6, + mockFont8x10, + []byte("" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000001400000000" + + "00000000000000000000141414000000" + + "00000000000000000014140014140000" + + "00000000000000001414000000141400" + + "00000000000000001414000000141400" + + "00000000000000001414141414141400" + + "00000000000000001414000000141400" + + "00000000000000001414000000141400" + + "00000000000000001414000000141400" + + "00000000000000001414000000141400", + ), + }, + { + 20, 20, 3, + mockFont10x14, + []byte("" + + "0000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000" + + "0000000000000000000000000000001400000000" + + "0000000000000000000000000000001400000000" + + "0000000000000000000000000000141414000000" + + "0000000000000000000000000000141414000000" + + "0000000000000000000000000014140014140000" + + "0000000000000000000000000014140014140000" + + "0000000000000000000000000014140000141400" + + "0000000000000000000000001414000000141400" + + "0000000000000000000000001414141414141400" + + "0000000000000000000000001414000000141400" + + "0000000000000000000000141400000000141400" + + "0000000000000000000000141400000000001414" + + "0000000000000000000000141400000000001414" + + "0000000000000000000014141414000000141414" + + "0000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000", + ), + }, + } + + var ( + // RGB555 + colorInfo = &multiboot.FramebufferRGBColorInfo{ + RedPosition: 10, + RedMaskSize: 5, + GreenPosition: 5, + GreenMaskSize: 5, + BluePosition: 0, + BlueMaskSize: 5, + } + fg = uint8(1) + fgColor = color.RGBA{R: 10, G: 0, B: 12} + bg = uint8(0) + ) + + for specIndex, spec := range specs { + fb := make([]uint8, spec.consW*spec.consH*2) + + cons := NewVesaFbConsole(spec.consW, spec.consH, 16, spec.consW*2, colorInfo, 0) + cons.fb = fb + cons.offsetY = spec.offsetY + cons.SetFont(spec.font) + cons.loadDefaultPalette() + cons.SetPaletteColor(fg, fgColor) + + // ASCII 0 maps to the a blank character in the mock font + // ASCII 1 maps to the letter 'A' in the mock font + cons.Write(0, fg, bg, 0, 0) + cons.Write(1, fg, bg, 2, 1) + + // Convert expected contents from ASCII to byte + for i := 0; i < len(spec.expFb); i++ { + spec.expFb[i] -= '0' + } + + if !reflect.DeepEqual(spec.expFb, fb) { + t.Errorf("[spec %d] unexpected frame buffer contents:\n%s", + specIndex, + diffFrameBuffer(spec.consW, spec.consH, spec.consW*2, spec.expFb, fb), + ) + } + } +} + func TestVesaFbWrite24bpp(t *testing.T) { specs := []struct { consW, consH, offsetY uint32 @@ -664,6 +764,227 @@ func TestVesaFbFill8(t *testing.T) { } } +func TestVesaFbFill16(t *testing.T) { + var ( + consW, consH uint32 = 16, 26 + // RGB555 + colorInfo = &multiboot.FramebufferRGBColorInfo{ + RedPosition: 10, + RedMaskSize: 5, + GreenPosition: 5, + GreenMaskSize: 5, + BluePosition: 0, + BlueMaskSize: 5, + } + bg uint8 = 1 + bgColor = color.RGBA{R: 10, G: 0, B: 12} + origFb = []byte("" + + "66666666666666666666666666666666" + // } + "77777777777777777777777777777777" + // }- reserved rows + "88888888888888888888888888888888" + // } + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000", + ) + ) + specs := []struct { + // Input rect in characters + x, y, w, h uint32 + offsetY uint32 + expFb []byte + }{ + { + 0, 0, 1, 1, + 0, + []byte("" + + "14141414141414146666666666666666" + // } + "14141414141414147777777777777777" + // }- reserved rows + "14141414141414148888888888888888" + // } + "14141414141414140000000000000000" + + "14141414141414140000000000000000" + + "14141414141414140000000000000000" + + "14141414141414140000000000000000" + + "14141414141414140000000000000000" + + "14141414141414140000000000000000" + + "14141414141414140000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000", + ), + }, + { + 2, 0, 10, 1, + 3, + []byte("" + + "66666666666666666666666666666666" + // } + "77777777777777777777777777777777" + // }- reserved rows + "88888888888888888888888888888888" + // } + "00000000000000001414141414141414" + + "00000000000000001414141414141414" + + "00000000000000001414141414141414" + + "00000000000000001414141414141414" + + "00000000000000001414141414141414" + + "00000000000000001414141414141414" + + "00000000000000001414141414141414" + + "00000000000000001414141414141414" + + "00000000000000001414141414141414" + + "00000000000000001414141414141414" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000", + ), + }, + { + 0, 0, 100, 100, + 3, + []byte("" + + "66666666666666666666666666666666" + // } + "77777777777777777777777777777777" + // }- reserved rows + "88888888888888888888888888888888" + // } + "14141414141414141414141414141414" + + "14141414141414141414141414141414" + + "14141414141414141414141414141414" + + "14141414141414141414141414141414" + + "14141414141414141414141414141414" + + "14141414141414141414141414141414" + + "14141414141414141414141414141414" + + "14141414141414141414141414141414" + + "14141414141414141414141414141414" + + "14141414141414141414141414141414" + + "14141414141414141414141414141414" + + "14141414141414141414141414141414" + + "14141414141414141414141414141414" + + "14141414141414141414141414141414" + + "14141414141414141414141414141414" + + "14141414141414141414141414141414" + + "14141414141414141414141414141414" + + "14141414141414141414141414141414" + + "14141414141414141414141414141414" + + "14141414141414141414141414141414" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000", + ), + }, + { + 100, 100, 1, 1, + 6, + []byte("" + + "66666666666666666666666666666666" + // } + "77777777777777777777777777777777" + // }- reserved rows + "88888888888888888888888888888888" + // } + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000000000000000000000" + + "00000000000000001414141414141414" + + "00000000000000001414141414141414" + + "00000000000000001414141414141414" + + "00000000000000001414141414141414" + + "00000000000000001414141414141414" + + "00000000000000001414141414141414" + + "00000000000000001414141414141414" + + "00000000000000001414141414141414" + + "00000000000000001414141414141414" + + "00000000000000001414141414141414", + ), + }, + } + + // Convert original fb contents from ASCII to byte + for i := 0; i < len(origFb); i++ { + origFb[i] -= '0' + } + + for specIndex, spec := range specs { + // Convert expected contents from ASCII to byte + for i := 0; i < len(spec.expFb); i++ { + spec.expFb[i] -= '0' + } + + fb := make([]uint8, consW*consH*2) + copy(fb, origFb) + + cons := NewVesaFbConsole(consW, consH, 16, consW*2, colorInfo, 0) + cons.fb = fb + cons.offsetY = spec.offsetY + cons.loadDefaultPalette() + cons.SetPaletteColor(bg, bgColor) + + // Calling fill before selecting a font should be a no-op + cons.Fill(spec.x, spec.y, spec.w, spec.h, 0, bg) + if !reflect.DeepEqual(origFb, fb) { + t.Errorf("[spec %d] unexpected frame buffer contents:\n%s", + specIndex, + diffFrameBuffer(consW, consH, consW*3, origFb, fb), + ) + } + + cons.SetFont(mockFont8x10) + + cons.Fill(spec.x, spec.y, spec.w, spec.h, 0, bg) + + if !reflect.DeepEqual(spec.expFb, fb) { + t.Errorf("[spec %d] unexpected frame buffer contents:\n%s", + specIndex, + diffFrameBuffer(consW, consH, consW*2, spec.expFb, fb), + ) + } + } +} + func TestVesaFbFill24(t *testing.T) { var ( consW, consH uint32 = 16, 26 @@ -968,6 +1289,69 @@ func TestVesaFbPalette(t *testing.T) { } } +func TestVesaFbReplace16(t *testing.T) { + var ( + consW, consH uint32 = 4, 4 + // BGR + colorInfo = &multiboot.FramebufferRGBColorInfo{ + RedPosition: 10, + RedMaskSize: 5, + GreenPosition: 5, + GreenMaskSize: 5, + BluePosition: 0, + BlueMaskSize: 5, + } + ) + + specs := []struct { + bpp uint8 + inpFb []byte + expFb []byte + }{ + { + 24, + []byte{ + 0x00, 0x00, 0x12, 0x00, 0x00, 0x34, 0x34, 0x12, + 0x00, 0x00, 0x12, 0x00, 0x00, 0x34, 0x34, 0x12, + 0x00, 0x00, 0x12, 0x00, 0x00, 0x34, 0x34, 0x12, + 0x00, 0x00, 0x12, 0x00, 0x00, 0x34, 0x34, 0x12, + }, + []byte{ + 0x34, 0x12, 0x12, 0x00, 0x00, 0x34, 0x34, 0x12, + 0x34, 0x12, 0x12, 0x00, 0x00, 0x34, 0x34, 0x12, + 0x34, 0x12, 0x12, 0x00, 0x00, 0x34, 0x34, 0x12, + 0x34, 0x12, 0x12, 0x00, 0x00, 0x34, 0x34, 0x12, + }, + }, + } + + for specIndex, spec := range specs { + fb := make([]uint8, consW*consH*2) + copy(fb, spec.inpFb) + + cons := NewVesaFbConsole(consW, consH, 16, consW*2, colorInfo, 0) + cons.fb = fb + cons.palette = make(color.Palette, 1) + + // First color update should not trigger a replace as the color is not used yet + cons.SetPaletteColor(0, color.RGBA{}) + if !reflect.DeepEqual(spec.inpFb, fb) { + t.Errorf("[spec %d] unexpected frame buffer contents:\n%s", + specIndex, + diffFrameBuffer(consW, consH, cons.pitch, spec.expFb, fb), + ) + } + + // Second color update should replace existing pixels with the new RGB value + cons.SetPaletteColor(0, color.RGBA{R: 32, G: 136, B: 160}) + if !reflect.DeepEqual(spec.expFb, fb) { + t.Errorf("[spec %d] unexpected frame buffer contents:\n%s", + specIndex, + diffFrameBuffer(consW, consH, cons.pitch, spec.expFb, fb), + ) + } + } +} func TestVesaFbReplace24(t *testing.T) { var ( consW, consH uint32 = 4, 4 @@ -1112,6 +1496,58 @@ func TestVesaFbProbe(t *testing.T) { } } +func TestVesaFbPackColor16(t *testing.T) { + specs := []struct { + colorInfo *multiboot.FramebufferRGBColorInfo + input color.RGBA + exp uint16 + }{ + { + // RGB555 + &multiboot.FramebufferRGBColorInfo{ + RedPosition: 10, + RedMaskSize: 5, + GreenPosition: 5, + GreenMaskSize: 5, + BluePosition: 0, + BlueMaskSize: 5, + }, + color.RGBA{R: 32, G: 136, B: 160}, + 0x1234, + }, + { + // RGB565 + &multiboot.FramebufferRGBColorInfo{ + RedPosition: 11, + RedMaskSize: 5, + GreenPosition: 5, + GreenMaskSize: 6, + BluePosition: 0, + BlueMaskSize: 5, + }, + color.RGBA{R: 250, G: 200, B: 120}, + (250>>3)<<11 | (200>>2)<<5 | (120 >> 3), + }, + } + + cons := NewVesaFbConsole(0, 0, 16, 0, nil, 0) + cons.palette = make(color.Palette, 256) + + for specIndex, spec := range specs { + cons.colorInfo = spec.colorInfo + cons.palette[0] = spec.input + + exp := [2]uint8{ + uint8(spec.exp), + uint8(spec.exp >> 8), + } + + if got := cons.packColor16(0); got != exp { + t.Errorf("[spec %d] expected: %v; got %v", specIndex, exp, got) + } + } +} + func TestVesaFbPackColor24(t *testing.T) { specs := []struct { colorInfo *multiboot.FramebufferRGBColorInfo