Refactoring #3
@@ -3,17 +3,20 @@ package renderer
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"mxl-player/internal/sdl"
|
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
"mxl-player/internal/sdl"
|
||||||
|
|
||||||
"github.com/christerso/vulkan-go/vk"
|
"github.com/christerso/vulkan-go/vk"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
// Raises by swapchain creation, whem windos is minimized
|
// Raises by swapchain creation, whem windos is minimized
|
||||||
var ErrMinimized = errors.New("window minimized")
|
ErrMinimized = errors.New("window minimized")
|
||||||
|
|
||||||
// ErrOutOfDate is returned by DrawFrame when the swapchain needs recreation.
|
// ErrOutOfDate is returned by DrawFrame when the swapchain needs recreation.
|
||||||
var ErrOutOfDate = errors.New("swapchain out of date")
|
ErrOutOfDate = errors.New("swapchain out of date")
|
||||||
|
ErrInvalidVideoFrame = errors.New("invalid video frame")
|
||||||
|
)
|
||||||
|
|
||||||
// shader push-constants block
|
// shader push-constants block
|
||||||
type PushConstants struct {
|
type PushConstants struct {
|
||||||
@@ -392,6 +395,64 @@ func (r *Renderer) RecreateBuffers(newSize vk.DeviceSize) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Renderer) StageFrame(
|
||||||
|
payload []byte,
|
||||||
|
width uint32,
|
||||||
|
height uint32,
|
||||||
|
stride uint32,
|
||||||
|
) error {
|
||||||
|
frameSize, err := validateFramePayload(len(payload), width, height, stride)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if frameSize != r.FrameSize() {
|
||||||
|
// RecreateBuffers waits for the device to become idle.
|
||||||
|
if err := r.RecreateBuffers(frameSize); err != nil {
|
||||||
|
return fmt.Errorf("resize video buffers: %w", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// The previous submitted frame may still read the mapped staging buffer.
|
||||||
|
if err := r.dev.WaitFence(r.inFlight, ^uint64(0)); err != nil {
|
||||||
|
return fmt.Errorf("wait before staging video: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vk.CopyToMapped(
|
||||||
|
r.StagingMapped(),
|
||||||
|
payload[:int(frameSize)],
|
||||||
|
)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateFramePayload(
|
||||||
|
payloadLen int,
|
||||||
|
width uint32,
|
||||||
|
height uint32,
|
||||||
|
stride uint32,
|
||||||
|
) (vk.DeviceSize, error) {
|
||||||
|
if width == 0 || height == 0 || stride == 0 {
|
||||||
|
return 0, fmt.Errorf(
|
||||||
|
"%w: dimensions=%dx%d stride=%d",
|
||||||
|
ErrInvalidVideoFrame,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
stride,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
requiredSize := uint64(stride) * uint64(height)
|
||||||
|
if payloadLen < 0 || requiredSize > uint64(payloadLen) {
|
||||||
|
return 0, fmt.Errorf(
|
||||||
|
"%w: payload=%d required=%d",
|
||||||
|
ErrInvalidVideoFrame,
|
||||||
|
payloadLen,
|
||||||
|
requiredSize,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return vk.DeviceSize(requiredSize), nil
|
||||||
|
}
|
||||||
|
|
||||||
// DrawFrame acquires an image, records commands, submits, and presents.
|
// DrawFrame acquires an image, records commands, submits, and presents.
|
||||||
// Returns ErrOutOfDate if the swapchain needs recreation
|
// Returns ErrOutOfDate if the swapchain needs recreation
|
||||||
func (r *Renderer) DrawFrame(videoW, videoH, stride uint32) error {
|
func (r *Renderer) DrawFrame(videoW, videoH, stride uint32) error {
|
||||||
|
|||||||
@@ -0,0 +1,122 @@
|
|||||||
|
package renderer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/christerso/vulkan-go/vk"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestValidateFramePayload(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
payloadLen int
|
||||||
|
width uint32
|
||||||
|
height uint32
|
||||||
|
stride uint32
|
||||||
|
wantSize vk.DeviceSize
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid frame",
|
||||||
|
payloadLen: 5120 * 1080,
|
||||||
|
width: 1920,
|
||||||
|
height: 1080,
|
||||||
|
stride: 5120,
|
||||||
|
wantSize: vk.DeviceSize(5120 * 1080),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "payload may be larger than frame",
|
||||||
|
payloadLen: 5120*1080 + 128,
|
||||||
|
width: 1920,
|
||||||
|
height: 1080,
|
||||||
|
stride: 5120,
|
||||||
|
wantSize: vk.DeviceSize(5120 * 1080),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "zero width",
|
||||||
|
payloadLen: 100,
|
||||||
|
height: 10,
|
||||||
|
stride: 10,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "zero height",
|
||||||
|
payloadLen: 100,
|
||||||
|
width: 10,
|
||||||
|
stride: 10,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "zero stride",
|
||||||
|
payloadLen: 100,
|
||||||
|
width: 10,
|
||||||
|
height: 10,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "payload is too small",
|
||||||
|
payloadLen: 99,
|
||||||
|
width: 10,
|
||||||
|
height: 10,
|
||||||
|
stride: 10,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "negative payload length",
|
||||||
|
payloadLen: -1,
|
||||||
|
width: 10,
|
||||||
|
height: 10,
|
||||||
|
stride: 10,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := validateFramePayload(
|
||||||
|
tt.payloadLen,
|
||||||
|
tt.width,
|
||||||
|
tt.height,
|
||||||
|
tt.stride,
|
||||||
|
)
|
||||||
|
|
||||||
|
if tt.wantErr {
|
||||||
|
if !errors.Is(err, ErrInvalidVideoFrame) {
|
||||||
|
t.Fatalf("validateFramePayload() error = %v, want %v", err, ErrInvalidVideoFrame)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("validateFramePayload() error = %v, want nil", err)
|
||||||
|
}
|
||||||
|
if got != tt.wantSize {
|
||||||
|
t.Errorf("validateFramePayload() size = %d, want %d", got, tt.wantSize)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateFramePayloadUses64BitSize(t *testing.T) {
|
||||||
|
if strconv.IntSize < 64 {
|
||||||
|
t.Skip("test requires a 64-bit int")
|
||||||
|
}
|
||||||
|
|
||||||
|
stride := ^uint32(0)
|
||||||
|
height := uint32(2)
|
||||||
|
required := uint64(stride) * uint64(height)
|
||||||
|
|
||||||
|
got, err := validateFramePayload(
|
||||||
|
int(required),
|
||||||
|
1,
|
||||||
|
height,
|
||||||
|
stride,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("validateFramePayload() error = %v, want nil", err)
|
||||||
|
}
|
||||||
|
if uint64(got) != required {
|
||||||
|
t.Fatalf("validateFramePayload() size = %d, want %d", got, required)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user