From 85cef0dfec1fcadfb56b772d0d52d2cecfd748d2 Mon Sep 17 00:00:00 2001 From: Sergey Stepanov Date: Fri, 22 Sep 2023 17:46:00 +0300 Subject: [PATCH] Convert colors in C --- pkg/worker/caged/libretro/image/canvas.c | 142 +++++++++ pkg/worker/caged/libretro/image/canvas.go | 120 +++----- pkg/worker/caged/libretro/image/canvas.h | 42 +++ .../caged/libretro/image/canvas_test.go | 290 ++++++++++++++++-- pkg/worker/caged/libretro/image/rotation.go | 87 ------ .../caged/libretro/image/rotation_test.go | 247 --------------- pkg/worker/caged/libretro/image/scale.go | 27 -- .../caged/libretro/nanoarch/nanoarch.go | 44 +-- 8 files changed, 513 insertions(+), 486 deletions(-) create mode 100644 pkg/worker/caged/libretro/image/canvas.c create mode 100644 pkg/worker/caged/libretro/image/canvas.h delete mode 100644 pkg/worker/caged/libretro/image/rotation.go delete mode 100644 pkg/worker/caged/libretro/image/rotation_test.go delete mode 100644 pkg/worker/caged/libretro/image/scale.go diff --git a/pkg/worker/caged/libretro/image/canvas.c b/pkg/worker/caged/libretro/image/canvas.c new file mode 100644 index 000000000..66b00cd83 --- /dev/null +++ b/pkg/worker/caged/libretro/image/canvas.c @@ -0,0 +1,142 @@ +#include "canvas.h" + +__inline int rot_x(int t, int x, int y, int w, int h) { + switch (t) { + case 1: + return r90_x(x,y,w,h); + break; + case 2: + return r180_x(x,y,w,h); + break; + case 3: + return r270_x(x,y,w,h); + break; + case 4: + return fy180_x(x,y,w,h); + break; + } + return x; +} + +__inline int rot_y(int t, int x, int y, int w, int h) { + switch (t) { + case 1: + return r90_y(x,y,w,h); + break; + case 2: + return r180_y(x,y,w,h); + break; + case 3: + return r270_y(x,y,w,h); + break; + case 4: + return fy180_y(x,y,w,h); + break; + } + return y; + } + + +void RGBA(int pix, void *destination, void *source, int yy, int yn, int xw, int xh, int dw, int pad, int rot) { + switch (pix) { + case BIT_SHORT5551: + break; + case BIT_INT_8888REV: + if (rot == 0) { + i8888(destination, source, yy, yn, xw, pad); + } else { + i8888r(destination, source, yy, yn, xw, xh, dw, pad, rot); + } + break; + case BIT_SHORT565: + if (rot == 0) { + i565(destination, source, yy, yn, xw, pad); + } else { + i565r(destination, source, yy, yn, xw, xh, dw, pad, rot); + } + break; + } +} + +void i565(void *destination, void *source, int yy, int yn, int xw, int pad) { + uint8_t *src = source; // must be in bytes because of possible padding in bytes + uint32_t *dst = destination; + + int y, x; + uint32_t px; + + for (y = yy; y < yn; ++y) { + for (x = 0; x < xw; ++x) { + px = *(uint16_t *)src; + src += 2; + *dst++ = _565(px); + } + src += pad; + } +} + +void i565r(void *destination, void *source, int yy, int yn, int xw, int xh, int dw, int pad, int rot) { + uint8_t *src = source; + uint32_t *dst = destination; + + uint32_t px; + + int x, y, dx, dy; + + for (y = yy; y < yn; ++y) { + for (x = 0; x < xw; ++x) { + px = *(uint16_t *)src; + src += 2; + + dx = rot_x(rot, x, y, xw, xh); + dy = rot_y(rot, x, y, xw, xh); + + dst[dx+dy*dw] = _565(px); + } + src += pad; + } +} + +void i8888r(void *destination, void *source, int yy, int yn, int xw, int xh, int dw, int pad, int rot) { + uint8_t *src = source; + uint32_t *dst = destination; + + int y, x; + uint32_t px; + + int dx, dy; + + for (y = yy; y < yn; ++y) { + for (x = 0; x < xw; ++x) { + px = *(uint32_t *)src; + + dx = rot_x(rot, x, y, xw, xh); + dy = rot_y(rot, x, y, xw, xh); + + dst[dx+dy*dw] = _8888rev(px); + src += 4; + } + src += pad; + } +} + +void i8888(void *destination, void *source, int yy, int yn, int xw, int pad) { + uint8_t *src = source; // must be in bytes because of possible padding in bytes + uint32_t *dst = destination; + + int y, x; + uint32_t px; + + for (y = yy; y < yn; ++y) { + for (x = 0; x < xw; ++x) { + px = *(uint32_t *)src; + src += 4; + *dst++ = _8888rev(px); + } + src += pad; + } +} + +uint32_t px8888rev(uint32_t px) { + return _8888rev(px); +} diff --git a/pkg/worker/caged/libretro/image/canvas.go b/pkg/worker/caged/libretro/image/canvas.go index c5f321ca6..035876a51 100644 --- a/pkg/worker/caged/libretro/image/canvas.go +++ b/pkg/worker/caged/libretro/image/canvas.go @@ -2,11 +2,18 @@ package image import ( "image" - "math/bits" "sync" "unsafe" + + "golang.org/x/image/draw" ) +/* +#cgo CFLAGS: -Wall +#include "canvas.h" +*/ +import "C" + // Canvas is a stateful drawing surface, i.e. image.RGBA type Canvas struct { enabled bool @@ -30,6 +37,35 @@ const ( BitFormatShort565 // BIT_FORMAT_SHORT_5_6_5 has 5 bits R, 6 bits G, 5 bits ) +const ( + ScaleNot = iota // skips image interpolation + ScaleNearestNeighbour // nearest neighbour interpolation + ScaleBilinear // bilinear interpolation +) + +func Resize(scaleType int, src *image.RGBA, out *image.RGBA) { + // !to do set it once instead switching on each iteration + switch scaleType { + case ScaleBilinear: + draw.ApproxBiLinear.Scale(out, out.Bounds(), src, src.Bounds(), draw.Src, nil) + case ScaleNot: + fallthrough + case ScaleNearestNeighbour: + fallthrough + default: + draw.NearestNeighbor.Scale(out, out.Bounds(), src, src.Bounds(), draw.Src, nil) + } +} + +type Rotation uint + +const ( + A90 Rotation = iota + 1 + A180 + A270 + F180 // F180 is flipped Y +) + func NewCanvas(w, h, size int) *Canvas { return &Canvas{ enabled: true, @@ -66,17 +102,17 @@ func (c *Canvas) Put(i *Frame) { func (c *Canvas) Clear() { c.wg = sync.WaitGroup{} } func (c *Canvas) SetEnabled(enabled bool) { c.enabled = enabled } -func (c *Canvas) Draw(encoding uint32, rot *Rotate, w, h, packedW, bpp int, data []byte, th int) *Frame { +func (c *Canvas) Draw(encoding uint32, rot Rotation, w, h, packedW, bpp int, data []byte, th int) *Frame { dst := c.Get(w, h) if th == 0 { - frame(encoding, dst, data, 0, h, h, w, packedW, bpp, rot) + frame(encoding, dst, data, 0, h, w, h, packedW, bpp, rot) } else { hn := h / th c.wg.Add(th) for i := 0; i < th; i++ { xx := hn * i go func() { - frame(encoding, dst, data, xx, hn, h, w, packedW, bpp, rot) + frame(encoding, dst, data, xx, hn, w, h, packedW, bpp, rot) c.wg.Done() }() } @@ -100,77 +136,23 @@ func (c *Canvas) Draw(encoding uint32, rot *Rotate, w, h, packedW, bpp int, data return dst } -func frame(encoding uint32, dst *Frame, data []byte, yy int, hn int, h int, w int, pwb int, bpp int, rot *Rotate) { +func frame(encoding uint32, dst *Frame, data []byte, yy int, hn int, w int, h int, pwb int, bpp int, rot Rotation) { sPtr := unsafe.Pointer(&data[yy*pwb]) dPtr := unsafe.Pointer(&dst.Pix[yy*dst.Stride]) // some cores can zero-right-pad rows to the packed width value pad := pwb - w*bpp - yn := yy + hn - - if rot == nil { - // LE, BE might not work - switch encoding { - case BitFormatShort565: - for y := yy; y < yn; y++ { - for x := 0; x < w; x++ { - i565((*uint32)(dPtr), uint32(*(*uint16)(sPtr))) - sPtr = unsafe.Add(sPtr, uintptr(bpp)) - dPtr = unsafe.Add(dPtr, uintptr(4)) - } - if pad > 0 { - sPtr = unsafe.Add(sPtr, uintptr(pad)) - } - } - case BitFormatInt8888Rev: - for y := yy; y < yn; y++ { - for x := 0; x < w; x++ { - ix8888((*uint32)(dPtr), *(*uint32)(sPtr)) - sPtr = unsafe.Add(sPtr, uintptr(bpp)) - dPtr = unsafe.Add(dPtr, uintptr(4)) - } - if pad > 0 { - sPtr = unsafe.Add(sPtr, uintptr(pad)) - } - } - } - } else { - switch encoding { - case BitFormatShort565: - for y := yy; y < yn; y++ { - for x, k := 0, 0; x < w; x++ { - dx, dy := rot.Call(x, y, w, h) - k = dx<<2 + dy*dst.Stride - dPtr = unsafe.Pointer(&dst.Pix[k]) - i565((*uint32)(dPtr), uint32(*(*uint16)(sPtr))) - sPtr = unsafe.Add(sPtr, uintptr(bpp)) - } - if pad > 0 { - sPtr = unsafe.Add(sPtr, uintptr(pad)) - } - } - case BitFormatInt8888Rev: - for y := yy; y < yn; y++ { - for x, k := 0, 0; x < w; x++ { - dx, dy := rot.Call(x, y, w, h) - k = dx<<2 + dy*dst.Stride - dPtr = unsafe.Pointer(&dst.Pix[k]) - ix8888((*uint32)(dPtr), *(*uint32)(sPtr)) - sPtr = unsafe.Add(sPtr, uintptr(bpp)) - } - if pad > 0 { - sPtr = unsafe.Add(sPtr, uintptr(pad)) - } - } - } + if pad < 0 { + pad = 0 } + if rot != 0 { + dPtr = unsafe.Pointer(&dst.Pix[0]) + } + C.RGBA(C.int(encoding), dPtr, sPtr, C.int(yy), C.int(yy+hn), C.int(w), C.int(h), C.int(dst.Stride>>2), C.int(pad), C.int(rot)) } -func i565(dst *uint32, px uint32) { - *dst = (px >> 8 & 0xf8) | ((px >> 3 & 0xfc) << 8) | ((px << 3 & 0xfc) << 16) // | 0xff000000 - // setting the last byte to 255 allows saving RGBA images to PNG not as black squares -} +func _8888rev(px uint32) uint32 { return uint32(C.px8888rev(C.uint32_t(px))) } -func ix8888(dst *uint32, px uint32) { - //*dst = ((px >> 16) & 0xff) | (px & 0xff00) | ((px << 16) & 0xff0000) + 0xff000000 - *dst = bits.ReverseBytes32(px << 8) //| 0xff000000 +func rotate(t int, x int, y int, w int, h int) (int, int) { + return int(C.rot_x(C.int(t), C.int(x), C.int(y), C.int(w), C.int(h))), + int(C.rot_y(C.int(t), C.int(x), C.int(y), C.int(w), C.int(h))) } diff --git a/pkg/worker/caged/libretro/image/canvas.h b/pkg/worker/caged/libretro/image/canvas.h new file mode 100644 index 000000000..f89a0f273 --- /dev/null +++ b/pkg/worker/caged/libretro/image/canvas.h @@ -0,0 +1,42 @@ +#ifndef CANVAS_H__ +#define CANVAS_H__ + +#include + +#define BIT_SHORT5551 0 +#define BIT_INT_8888REV 1 +#define BIT_SHORT565 2 + +// Rotate90 is 90° CCW or 270° CW. +#define r90_x(x, y, w, h) ( y ) +#define r90_y(x, y, w, h) ( (w - 1) - x ) + +// Rotate180 is 180° CCW. +#define r180_x(x, y, w, h) ( (w - 1) - x ) +#define r180_y(x, y, w, h) ( (h - 1) - y ) + +// Rotate270 is 270° CCW or 90° CW. +#define r270_x(x, y, w, h) ( (h - 1) - y ) +#define r270_y(x, y, w, h) ( x ) + +// Flip Y +#define fy180_x(x, y, w, h) ( x ) +#define fy180_y(x, y, w, h) ( (h - 1) - y ) + +int rot_x(int t, int x, int y, int w, int h); +int rot_y(int t, int x, int y, int w, int h); + +#define _565(x) ((x >> 8 & 0xf8) | ((x >> 3 & 0xfc) << 8) | ((x << 3 & 0xfc) << 16)); // | 0xff000000 +#define _8888rev(px) (((px >> 16) & 0xff) | (px & 0xff00) | ((px << 16) & 0xff0000)); // | 0xff000000) + + +void RGBA(int pix, void *destination, void *source, int yy, int yn, int xw, int xh, int dw, int pad, int rot); + +void i565(void *destination, void *source, int yy, int yn, int xw, int pad); +void i8888(void *destination, void *source, int yy, int yn, int xw, int pad); +void i565r(void *destination, void *source, int yy, int yn, int xw, int xh, int dw, int pad, int rot); +void i8888r(void *destination, void *source, int yy, int yn, int xw, int xh, int dw, int pad, int rot); + +uint32_t px8888rev(uint32_t px); + +#endif diff --git a/pkg/worker/caged/libretro/image/canvas_test.go b/pkg/worker/caged/libretro/image/canvas_test.go index a7bc47aff..b1def658f 100644 --- a/pkg/worker/caged/libretro/image/canvas_test.go +++ b/pkg/worker/caged/libretro/image/canvas_test.go @@ -1,14 +1,18 @@ package image import ( + "bytes" "fmt" "testing" ) func BenchmarkDraw(b *testing.B) { + w1, h1 := 256, 240 + w2, h2 := 640, 480 + type args struct { encoding uint32 - rot *Rotate + rot Rotation scaleType int w int h int @@ -24,35 +28,52 @@ func BenchmarkDraw(b *testing.B) { args args }{ { - name: "0th", + name: "565_0th", + args: args{ + encoding: BitFormatShort565, scaleType: ScaleNearestNeighbour, + w: w1, h: h1, packedW: w1, bpp: 2, data: make([]uint8, w1*h1*2), dw: w1, dh: h1, th: 0, + }, + }, + { + name: "565_0th_90", + args: args{ + encoding: BitFormatShort565, rot: A90, scaleType: ScaleNearestNeighbour, + w: h1, h: w1, packedW: h1, bpp: 2, data: make([]uint8, w1*h1*2), dw: w1, dh: h1, th: 0, + }, + }, + { + name: "565_0th", + args: args{ + encoding: BitFormatShort565, scaleType: ScaleNearestNeighbour, + w: w2, h: h2, packedW: w1, bpp: 2, data: make([]uint8, w2*h2*2), dw: w2, dh: h2, th: 0, + }, + }, + { + name: "565_4th", + args: args{ + encoding: BitFormatShort565, scaleType: ScaleNearestNeighbour, + w: w1, h: h1, packedW: w1, bpp: 2, data: make([]uint8, w1*h1*2), dw: w1, dh: h1, th: 4, + }, + }, + { + name: "565_4th", + args: args{ + encoding: BitFormatShort565, scaleType: ScaleNearestNeighbour, + w: w2, h: h2, packedW: w2, bpp: 2, data: make([]uint8, w2*h2*2), dw: w2, dh: h2, th: 4, + }, + }, + { + name: "8888 - 0th", args: args{ - encoding: BitFormatInt8888Rev, - rot: nil, - scaleType: ScaleNearestNeighbour, - w: 256, - h: 240, - packedW: 256, - bpp: 4, - data: make([]uint8, 256*240*4), - dw: 256, - dh: 240, - th: 0, + encoding: BitFormatInt8888Rev, scaleType: ScaleNearestNeighbour, + w: w1, h: h1, packedW: w1, bpp: 4, data: make([]uint8, w1*h1*4), dw: w1, dh: h1, th: 0, }, }, { - name: "4th", + name: "8888 - 4th", args: args{ - encoding: BitFormatInt8888Rev, - rot: nil, - scaleType: ScaleNearestNeighbour, - w: 256, - h: 240, - packedW: 256, - bpp: 4, - data: make([]uint8, 256*240*4), - dw: 256, - dh: 240, - th: 4, + encoding: BitFormatInt8888Rev, scaleType: ScaleNearestNeighbour, + w: w1, h: h1, packedW: w1, bpp: 4, data: make([]uint8, w1*h1*4), dw: w1, dh: h1, th: 4, }, }, } @@ -64,7 +85,7 @@ func BenchmarkDraw(b *testing.B) { img2 := c.Get(bn.args.dw, bn.args.dh) c.Put(img2) b.ResetTimer() - b.Run(fmt.Sprintf("%v", bn.name), func(b *testing.B) { + b.Run(fmt.Sprintf("%vx%v_%v", bn.args.w, bn.args.h, bn.name), func(b *testing.B) { for i := 0; i < b.N; i++ { p := c.Draw(bn.args.encoding, bn.args.rot, bn.args.w, bn.args.h, bn.args.packedW, bn.args.bpp, bn.args.data, bn.args.th) c.Put(p) @@ -95,10 +116,225 @@ func Test_ix8888(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - ix8888(tt.args.dst, tt.args.px) + *tt.args.dst = _8888rev(tt.args.px) if *tt.args.dst != tt.args.expect { t.Errorf("nope, %x %x", *tt.args.dst, tt.args.expect) } }) } } + +type dimensions struct { + w int + h int +} + +func TestRotate(t *testing.T) { + tests := []struct { + // packed bytes from a 2D matrix + input []byte + // original matrix's width + w int + // original matrix's height + h int + // rotation algorithm + rotateHow []Rotation + expected [][]byte + }{ + { + // a cross + []byte{ + 0, 1, 0, + 1, 1, 1, + 0, 1, 0, + }, + 3, 3, []Rotation{0, A90, A180, A270}, + [][]byte{ + { + 0, 1, 0, + 1, 1, 1, + 0, 1, 0, + }, + { + 0, 1, 0, + 1, 1, 1, + 0, 1, 0, + }, + { + 0, 1, 0, + 1, 1, 1, + 0, 1, 0, + }, + { + 0, 1, 0, + 1, 1, 1, + 0, 1, 0, + }, + }, + }, + { + []byte{ + 1, 2, + 3, 4, + 5, 6, + 7, 8, + }, + 2, 4, []Rotation{0, A90, A180, A270}, + [][]byte{ + { + 1, 2, + 3, 4, + 5, 6, + 7, 8, + }, + { + 2, 4, 6, 8, + 1, 3, 5, 7, + }, + { + 8, 7, + 6, 5, + 4, 3, + 2, 1, + }, + { + 7, 5, 3, 1, + 8, 6, 4, 2, + }, + }, + }, + { + // a square + []byte{ + 1, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 0, + 0, 1, 1, 1, 1, 1, 1, 0, + 0, 1, 0, 0, 0, 0, 1, 0, + 0, 1, 1, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 1, + }, + 8, 6, []Rotation{0, A90, A180, A270}, + [][]byte{ + { + // L // R + 1, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 0, + 0, 1, 1, 1, 1, 1, 1, 0, + 0, 1, 0, 0, 0, 0, 1, 0, + 0, 1, 1, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 1, + }, + { + 0, 0, 0, 0, 0, 1, + 0, 1, 1, 1, 1, 0, + 0, 1, 1, 0, 1, 0, + 0, 1, 1, 0, 1, 0, + 0, 1, 1, 0, 1, 0, + 0, 1, 1, 0, 1, 0, + 0, 1, 1, 1, 1, 0, + 1, 0, 0, 0, 0, 0, + }, + + { + 1, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 0, + 0, 1, 0, 0, 0, 0, 1, 0, + 0, 1, 1, 1, 1, 1, 1, 0, + 0, 1, 1, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 1, + }, + { + 0, 0, 0, 0, 0, 1, + 0, 1, 1, 1, 1, 0, + 0, 1, 0, 1, 1, 0, + 0, 1, 0, 1, 1, 0, + 0, 1, 0, 1, 1, 0, + 0, 1, 0, 1, 1, 0, + 0, 1, 1, 1, 1, 0, + 1, 0, 0, 0, 0, 0, + }, + }, + }, + } + + for _, test := range tests { + for i, rot := range test.rotateHow { + if output := exampleRotate(test.input, test.w, test.h, rot); !bytes.Equal(output, test.expected[i]) { + t.Errorf( + "Test fail for angle %v with %v that should be \n%v but it's \n%v", + rot, test.input, test.expected[i], output) + } + } + } +} + +func TestBoundsAfterRotation(t *testing.T) { + tests := []struct { + dim []dimensions + rotateHow []Rotation + }{ + { + // a combinatorics lib would be nice instead + []dimensions{ + // square + {w: 100, h: 100}, + // even w/h + {w: 100, h: 50}, + // even h/w + {w: 50, h: 100}, + // odd even w/h + {w: 77, h: 32}, + // even odd h/w + {w: 32, h: 77}, + // just odd + {w: 13, h: 19}, + }, + []Rotation{0, A90, A180, A270}, + }, + } + + for _, test := range tests { + for _, rot := range test.rotateHow { + for _, dim := range test.dim { + + for y := 0; y < dim.h; y++ { + for x := 0; x < dim.w; x++ { + + xx, yy := rotate(int(rot), x, y, dim.w, dim.h) + + if rot == A90 || rot == A270 { // is even + yy, xx = xx, yy + } + + if xx < 0 || xx > dim.w { + t.Errorf("Rot %v, coordinate x should be in range [0; %v]: %v", rot, dim.w-1, xx) + } + + if yy < 0 || yy > dim.h { + t.Errorf("Rot %v, coordinate y should be in range [0; %v]: %v", rot, dim.h-1, yy) + } + } + } + } + } + } +} + +// exampleRotate is an example of rotation usage. +// +// [1 2 3 4 5 6 7 8 9] +// [7 4 1 8 5 2 9 6 3] +func exampleRotate(data []uint8, w int, h int, rot Rotation) []uint8 { + dest := make([]uint8, len(data)) + for y := 0; y < h; y++ { + for x := 0; x < w; x++ { + nx, ny := rotate(int(rot), x, y, w, h) + stride := w + if rot == A90 || rot == A270 { // is even + stride = h + } + dest[nx+ny*stride] = data[x+y*w] + } + } + return dest +} diff --git a/pkg/worker/caged/libretro/image/rotation.go b/pkg/worker/caged/libretro/image/rotation.go deleted file mode 100644 index 950c89a27..000000000 --- a/pkg/worker/caged/libretro/image/rotation.go +++ /dev/null @@ -1,87 +0,0 @@ -// Package image contains functions for rotations of points in a 2-dimensional space. -package image - -type Angle uint - -const ( - Angle0 Angle = iota - Angle90 - Angle180 - Angle270 - Flip180 -) - -// Angles is a helper to choose appropriate rotation based on its angle. -var Angles = [5]Rotate{ - Angle0: {Angle: Angle0, Call: Rotate0}, - Angle90: {Angle: Angle90, Call: Rotate90, IsEven: true}, - Angle180: {Angle: Angle180, Call: Rotate180}, - Angle270: {Angle: Angle270, Call: Rotate270, IsEven: true}, - Flip180: {Angle: Flip180, Call: Invert180}, -} - -func GetRotation(angle Angle) Rotate { return Angles[angle] } - -// Rotate is an interface for rotation of a given point. -// -// With the coordinates x, y in the matrix of w x h. -// Returns a pair of new coordinates x, y in the resulting matrix. -// Be aware that w / h values are 0 index-based, -// and it's meant to be used with h corresponded -// to matrix height and y coordinate, and with w to x coordinate. -type Rotate struct { - Angle Angle - Call func(x, y, w, h int) (int, int) - IsEven bool -} - -// Rotate0 is 0° or the original orientation. -// -// 1 2 3 1 2 3 -// 4 5 6 -> 4 5 6 -// 7 8 9 7 8 9 -func Rotate0(x, y, _, _ int) (int, int) { return x, y } - -// Rotate90 is 90° CCW or 270° CW. -// -// 1 2 3 3 6 9 -// 4 5 6 -> 2 5 8 -// 7 8 9 1 4 7 -func Rotate90(x, y, w, _ int) (int, int) { return y, (w - 1) - x } - -// Rotate180 is 180° CCW. -// -// 1 2 3 9 8 7 -// 4 5 6 -> 6 5 4 -// 7 8 9 3 2 1 -func Rotate180(x, y, w, h int) (int, int) { return (w - 1) - x, (h - 1) - y } - -// Rotate270 is 270° CCW or 90° CW. -// -// 1 2 3 7 4 1 -// 4 5 6 -> 8 5 2 -// 7 8 9 9 6 3 -func Rotate270(x, y, _, h int) (int, int) { return (h - 1) - y, x } - -func Invert180(x, y, _, h int) (int, int) { return x, (h - 1) - y } - -// ExampleRotate is an example of rotation usage. -// -// [1 2 3 4 5 6 7 8 9] -// [7 4 1 8 5 2 9 6 3] -func ExampleRotate(data []uint8, w int, h int, angle Angle) []uint8 { - dest := make([]uint8, len(data)) - rotationFn := Angles[angle] - for y := 0; y < h; y++ { - for x := 0; x < w; x++ { - nx, ny := rotationFn.Call(x, y, w, h) - stride := w - if rotationFn.IsEven { - stride = h - } - //fmt.Printf("%v:%v (%v) -> %v:%v (%v)\n", x, y, n1, nx, ny, n2) - dest[nx+ny*stride] = data[x+y*w] - } - } - return dest -} diff --git a/pkg/worker/caged/libretro/image/rotation_test.go b/pkg/worker/caged/libretro/image/rotation_test.go deleted file mode 100644 index ec00ea6cf..000000000 --- a/pkg/worker/caged/libretro/image/rotation_test.go +++ /dev/null @@ -1,247 +0,0 @@ -package image - -import ( - "bytes" - "testing" -) - -type dimensions struct { - w int - h int -} - -func TestRotate(t *testing.T) { - tests := []struct { - // packed bytes from a 2D matrix - input []byte - // original matrix's width - w int - // original matrix's height - h int - // rotation algorithm - rotateHow []Angle - expected [][]byte - }{ - { - // a cross - []byte{ - 0, 1, 0, - 1, 1, 1, - 0, 1, 0, - }, - 3, 3, []Angle{Angle0, Angle90, Angle180, Angle270}, - [][]byte{ - { - 0, 1, 0, - 1, 1, 1, - 0, 1, 0, - }, - { - 0, 1, 0, - 1, 1, 1, - 0, 1, 0, - }, - { - 0, 1, 0, - 1, 1, 1, - 0, 1, 0, - }, - { - 0, 1, 0, - 1, 1, 1, - 0, 1, 0, - }, - }, - }, - { - []byte{ - 1, 2, - 3, 4, - 5, 6, - 7, 8, - }, - 2, 4, []Angle{Angle0, Angle90, Angle180, Angle270}, - [][]byte{ - { - 1, 2, - 3, 4, - 5, 6, - 7, 8, - }, - { - 2, 4, 6, 8, - 1, 3, 5, 7, - }, - { - 8, 7, - 6, 5, - 4, 3, - 2, 1, - }, - { - 7, 5, 3, 1, - 8, 6, 4, 2, - }, - }, - }, - { - // a square - []byte{ - 1, 0, 0, 0, 0, 0, 0, 0, - 0, 1, 1, 1, 1, 1, 1, 0, - 0, 1, 1, 1, 1, 1, 1, 0, - 0, 1, 0, 0, 0, 0, 1, 0, - 0, 1, 1, 1, 1, 1, 1, 0, - 0, 0, 0, 0, 0, 0, 0, 1, - }, - 8, 6, []Angle{Angle0, Angle90, Angle180, Angle270}, - [][]byte{ - { - // L // R - 1, 0, 0, 0, 0, 0, 0, 0, - 0, 1, 1, 1, 1, 1, 1, 0, - 0, 1, 1, 1, 1, 1, 1, 0, - 0, 1, 0, 0, 0, 0, 1, 0, - 0, 1, 1, 1, 1, 1, 1, 0, - 0, 0, 0, 0, 0, 0, 0, 1, - }, - { - 0, 0, 0, 0, 0, 1, - 0, 1, 1, 1, 1, 0, - 0, 1, 1, 0, 1, 0, - 0, 1, 1, 0, 1, 0, - 0, 1, 1, 0, 1, 0, - 0, 1, 1, 0, 1, 0, - 0, 1, 1, 1, 1, 0, - 1, 0, 0, 0, 0, 0, - }, - - { - 1, 0, 0, 0, 0, 0, 0, 0, - 0, 1, 1, 1, 1, 1, 1, 0, - 0, 1, 0, 0, 0, 0, 1, 0, - 0, 1, 1, 1, 1, 1, 1, 0, - 0, 1, 1, 1, 1, 1, 1, 0, - 0, 0, 0, 0, 0, 0, 0, 1, - }, - { - 0, 0, 0, 0, 0, 1, - 0, 1, 1, 1, 1, 0, - 0, 1, 0, 1, 1, 0, - 0, 1, 0, 1, 1, 0, - 0, 1, 0, 1, 1, 0, - 0, 1, 0, 1, 1, 0, - 0, 1, 1, 1, 1, 0, - 1, 0, 0, 0, 0, 0, - }, - }, - }, - } - - for _, test := range tests { - for i, rot := range test.rotateHow { - if output := ExampleRotate(test.input, test.w, test.h, rot); !bytes.Equal(output, test.expected[i]) { - t.Errorf( - "Test fail for angle %v with %v that should be \n%v but it's \n%v", - rot, test.input, test.expected[i], output) - } - } - } -} - -func TestBoundsAfterRotation(t *testing.T) { - tests := []struct { - dim []dimensions - rotateHow []Angle - }{ - { - // a combinatorics lib would be nice instead - []dimensions{ - // square - {w: 100, h: 100}, - // even w/h - {w: 100, h: 50}, - // even h/w - {w: 50, h: 100}, - // odd even w/h - {w: 77, h: 32}, - // even odd h/w - {w: 32, h: 77}, - // just odd - {w: 13, h: 19}, - }, - []Angle{Angle0, Angle90, Angle180, Angle270}, - }, - } - - for _, test := range tests { - for _, rot := range test.rotateHow { - rotationFn := Angles[rot] - for _, dim := range test.dim { - - for y := 0; y < dim.h; y++ { - for x := 0; x < dim.w; x++ { - - xx, yy := rotationFn.Call(x, y, dim.w, dim.h) - - if rotationFn.IsEven { - yy, xx = xx, yy - } - - if xx < 0 || xx > dim.w { - t.Errorf("Rot %v, coordinate x should be in range [0; %v]: %v", rot, dim.w-1, xx) - } - - if yy < 0 || yy > dim.h { - t.Errorf("Rot %v, coordinate y should be in range [0; %v]: %v", rot, dim.h-1, yy) - } - } - } - } - } - } -} - -func BenchmarkDirect(b *testing.B) { - for i := 0; i < b.N; i++ { - _, _ = Rotate90(1, 1, 2, 2) - } -} - -func BenchmarkLiteral(b *testing.B) { - fn := Rotate90 - for i := 0; i < b.N; i++ { - _, _ = fn(1, 1, 2, 2) - } -} - -func BenchmarkAssign(b *testing.B) { - fn := Angles[Angle90].Call - for i := 0; i < b.N; i++ { - _, _ = fn(1, 1, 2, 2) - } -} - -func BenchmarkMapReassign(b *testing.B) { - fn := Angles[Angle90].Call - for i := 0; i < b.N; i++ { - fn2 := fn - _, _ = fn2(1, 1, 2, 2) - } -} - -func BenchmarkMapDirect(b *testing.B) { - for i := 0; i < b.N; i++ { - _, _ = Angles[Angle90].Call(1, 1, 2, 2) - } -} - -func BenchmarkNewMapDirect(b *testing.B) { - fns := map[Angle]func(x, y, w, h int) (int, int){ - Angle90: Rotate90, - } - - for i := 0; i < b.N; i++ { - _, _ = fns[Angle90](1, 1, 2, 2) - } -} diff --git a/pkg/worker/caged/libretro/image/scale.go b/pkg/worker/caged/libretro/image/scale.go deleted file mode 100644 index 32750d734..000000000 --- a/pkg/worker/caged/libretro/image/scale.go +++ /dev/null @@ -1,27 +0,0 @@ -package image - -import ( - "image" - - "golang.org/x/image/draw" -) - -const ( - ScaleNot = iota // skips image interpolation - ScaleNearestNeighbour // nearest neighbour interpolation - ScaleBilinear // bilinear interpolation -) - -func Resize(scaleType int, src *image.RGBA, out *image.RGBA) { - // !to do set it once instead switching on each iteration - switch scaleType { - case ScaleBilinear: - draw.ApproxBiLinear.Scale(out, out.Bounds(), src, src.Bounds(), draw.Src, nil) - case ScaleNot: - fallthrough - case ScaleNearestNeighbour: - fallthrough - default: - draw.NearestNeighbor.Scale(out, out.Bounds(), src, src.Bounds(), draw.Src, nil) - } -} diff --git a/pkg/worker/caged/libretro/nanoarch/nanoarch.go b/pkg/worker/caged/libretro/nanoarch/nanoarch.go index a8105e258..e92defea6 100644 --- a/pkg/worker/caged/libretro/nanoarch/nanoarch.go +++ b/pkg/worker/caged/libretro/nanoarch/nanoarch.go @@ -44,7 +44,7 @@ type Nanoarch struct { } options *map[string]string reserved chan struct{} // limits concurrent use - Rot *image.Rotate + Rot image.Rotation serializeSize C.size_t stopped atomic.Bool sysAvInfo C.struct_retro_system_av_info @@ -82,19 +82,14 @@ type FrameInfo struct { } type Metadata struct { - LibPath string // the full path to some emulator lib - AudioSampleRate int - Fps float64 - BaseWidth int - BaseHeight int - Rotation image.Rotate - IsGlAllowed bool - UsesLibCo bool - AutoGlContext bool - HasMultitap bool - HasVFR bool - Options map[string]string - Hacks []string + LibPath string // the full path to some emulator lib + IsGlAllowed bool + UsesLibCo bool + AutoGlContext bool + HasMultitap bool + HasVFR bool + Options map[string]string + Hacks []string } // Nan0 is a global link for C callbacks to Go @@ -123,7 +118,7 @@ func NewNano(localPath string) *Nanoarch { func (n *Nanoarch) AudioSampleRate() int { return int(n.sysAvInfo.timing.sample_rate) } func (n *Nanoarch) VideoFramerate() int { return int(n.sysAvInfo.timing.fps) } -func (n *Nanoarch) IsPortrait() bool { return n.Rot != nil && n.Rot.IsEven } +func (n *Nanoarch) IsPortrait() bool { return n.Rot == image.A90 || n.Rot == image.A270 } func (n *Nanoarch) GeometryBase() (int, int) { return int(n.sysAvInfo.geometry.base_width), int(n.sysAvInfo.geometry.base_height) } @@ -256,8 +251,7 @@ func (n *Nanoarch) LoadGame(path string) error { n.stopped.Store(false) if n.Video.gl.enabled { - // flip Y coordinates of OpenGL - setRotation(uint(image.Flip180)) + setRotation(image.F180) // flip Y coordinates of OpenGL bufS := uint(n.sysAvInfo.geometry.max_width*n.sysAvInfo.geometry.max_height) * n.Video.BPP graphics.SetBuffer(int(bufS)) n.log.Info().Msgf("Set buffer: %v", byteCountBinary(int64(bufS))) @@ -387,17 +381,9 @@ func videoSetPixelFormat(format uint32) (C.bool, error) { return true, nil } -func setRotation(rotation uint) { - if Nan0.Rot != nil && rotation == uint(Nan0.Rot.Angle) { - return - } - if rotation > 0 { - r := image.GetRotation(image.Angle(rotation)) - Nan0.Rot = &r - } else { - Nan0.Rot = nil - } - Nan0.log.Debug().Msgf("Image rotated %v°", map[uint]uint{0: 0, 1: 90, 2: 180, 3: 270}[rotation]) +func setRotation(rotation image.Rotation) { + Nan0.Rot = rotation + Nan0.log.Debug().Msgf("Image rotated %v°", map[uint]uint{0: 0, 1: 90, 2: 180, 3: 270}[uint(rotation)]) } func printOpenGLDriverInfo() { @@ -679,7 +665,7 @@ func coreEnvironment(cmd C.unsigned, data unsafe.Pointer) C.bool { switch cmd { case C.RETRO_ENVIRONMENT_SET_ROTATION: - setRotation(*(*uint)(data) % 4) + setRotation(image.Rotation(*(*uint)(data) % 4)) return true case C.RETRO_ENVIRONMENT_GET_CAN_DUPE: *(*C.bool)(data) = C.bool(true)