makefile fix
This commit is contained in:
@@ -1,302 +0,0 @@
|
||||
// OpenCL pattern gen
|
||||
package generator
|
||||
|
||||
/*
|
||||
#cgo linux LDFLAGS: -lOpenCL
|
||||
#cgo windows LDFLAGS: -lOpenCL
|
||||
|
||||
#include <CL/cl.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static cl_program build_cl_program(cl_context ctx, cl_device_id dev, const char* src, cl_int* err) {
|
||||
cl_program prog = clCreateProgramWithSource(ctx, 1, &src, NULL, err);
|
||||
if (*err != CL_SUCCESS) return NULL;
|
||||
*err = clBuildProgram(prog, 1, &dev, NULL, NULL, NULL);
|
||||
return prog;
|
||||
}
|
||||
|
||||
static const char* get_build_log(cl_program prog, cl_device_id dev) {
|
||||
static char buf[4096];
|
||||
size_t n = 0;
|
||||
if (clGetProgramBuildInfo(prog, dev, CL_PROGRAM_BUILD_LOG, sizeof(buf) - 1, buf, &n) != CL_SUCCESS) return "";
|
||||
buf[n < sizeof(buf) - 1 ? n : sizeof(buf) - 1] = '\0';
|
||||
return buf;
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type OpenCLGenerator struct {
|
||||
ctx C.cl_context
|
||||
queue C.cl_command_queue
|
||||
kernel C.cl_kernel
|
||||
prog C.cl_program
|
||||
dev C.cl_device_id
|
||||
atlasBuf C.cl_mem
|
||||
textBuf C.cl_mem
|
||||
width int
|
||||
height int
|
||||
}
|
||||
|
||||
// TextConfig describes a burn-in text overlay rendered by the kernel
|
||||
// with the baked 8x16 bitmap font. FG/BG are 10-bit {Y, Cb, Cr}.
|
||||
type TextConfig struct {
|
||||
Str string
|
||||
X, Y int
|
||||
Scale int
|
||||
FG [3]uint32
|
||||
BG [3]uint32
|
||||
Boxed bool
|
||||
}
|
||||
|
||||
func (g *OpenCLGenerator) setArg(idx int, size C.size_t, p unsafe.Pointer) error {
|
||||
if err := C.clSetKernelArg(g.kernel, C.cl_uint(idx), size, p); err != C.CL_SUCCESS {
|
||||
return fmt.Errorf("opencl: set arg %d failed: %d", idx, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// setTextArgs pushes the overlay arguments (kernel args 5..16) to the kernel.
|
||||
// buf is the text byte buffer; a placeholder is allowed when len(cfg.Str) == 0,
|
||||
// because the kernel checks text_len before dereferencing the text pointer.
|
||||
func (g *OpenCLGenerator) setTextArgs(buf C.cl_mem, cfg TextConfig) error {
|
||||
textLen := C.int(len(cfg.Str))
|
||||
tx, ty := C.int(cfg.X), C.int(cfg.Y)
|
||||
scale := C.int(cfg.Scale)
|
||||
if scale < 1 {
|
||||
scale = 1
|
||||
}
|
||||
hasBG := C.int(0)
|
||||
if cfg.Boxed {
|
||||
hasBG = 1
|
||||
}
|
||||
fgY, fgCb, fgCr := C.cl_uint(cfg.FG[0]), C.cl_uint(cfg.FG[1]), C.cl_uint(cfg.FG[2])
|
||||
bgY, bgCb, bgCr := C.cl_uint(cfg.BG[0]), C.cl_uint(cfg.BG[1]), C.cl_uint(cfg.BG[2])
|
||||
args := []struct {
|
||||
idx int
|
||||
p unsafe.Pointer
|
||||
sz C.size_t
|
||||
}{
|
||||
{5, unsafe.Pointer(&buf), C.sizeof_cl_mem},
|
||||
{6, unsafe.Pointer(&textLen), C.sizeof_int},
|
||||
{7, unsafe.Pointer(&tx), C.sizeof_int},
|
||||
{8, unsafe.Pointer(&ty), C.sizeof_int},
|
||||
{9, unsafe.Pointer(&scale), C.sizeof_int},
|
||||
{10, unsafe.Pointer(&hasBG), C.sizeof_int},
|
||||
{11, unsafe.Pointer(&fgY), C.sizeof_cl_uint},
|
||||
{12, unsafe.Pointer(&fgCb), C.sizeof_cl_uint},
|
||||
{13, unsafe.Pointer(&fgCr), C.sizeof_cl_uint},
|
||||
{14, unsafe.Pointer(&bgY), C.sizeof_cl_uint},
|
||||
{15, unsafe.Pointer(&bgCb), C.sizeof_cl_uint},
|
||||
{16, unsafe.Pointer(&bgCr), C.sizeof_cl_uint},
|
||||
}
|
||||
for _, a := range args {
|
||||
if err := g.setArg(a.idx, a.sz, a.p); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetText installs (or, with an empty string, removes) the burn-in overlay.
|
||||
func (g *OpenCLGenerator) SetText(cfg TextConfig) error {
|
||||
if len(cfg.Str) > 0 && (cfg.Scale < 1 || cfg.Scale > 8) {
|
||||
return fmt.Errorf("opencl: text scale must be 1..8, got %d", cfg.Scale)
|
||||
}
|
||||
if len(cfg.Str) > 1024 {
|
||||
return fmt.Errorf("opencl: text too long: %d bytes", len(cfg.Str))
|
||||
}
|
||||
|
||||
var err C.cl_int
|
||||
buf := g.atlasBuf // placeholder: text_len = 0 keeps the kernel off it
|
||||
if len(cfg.Str) > 0 {
|
||||
cData := C.CBytes([]byte(cfg.Str))
|
||||
defer C.free(cData)
|
||||
buf = C.clCreateBuffer(g.ctx, C.CL_MEM_READ_ONLY|C.CL_MEM_COPY_HOST_PTR,
|
||||
C.size_t(len(cfg.Str)), cData, &err)
|
||||
if err != C.CL_SUCCESS {
|
||||
return fmt.Errorf("opencl: text buffer upload failed: %d", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := g.setTextArgs(buf, cfg); err != nil {
|
||||
if len(cfg.Str) > 0 {
|
||||
C.clReleaseMemObject(buf)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if g.textBuf != nil && g.textBuf != buf {
|
||||
C.clReleaseMemObject(g.textBuf)
|
||||
}
|
||||
if len(cfg.Str) > 0 {
|
||||
g.textBuf = buf
|
||||
} else {
|
||||
g.textBuf = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewOpenCLGenerator(width, height uint, kernelPath string) (*OpenCLGenerator, error) {
|
||||
var err C.cl_int
|
||||
var platform C.cl_platform_id
|
||||
|
||||
if err = C.clGetPlatformIDs(1, &platform, nil); err != C.CL_SUCCESS {
|
||||
return nil, fmt.Errorf("opencl: platform not found: %d", err)
|
||||
}
|
||||
|
||||
g := &OpenCLGenerator{width: int(width), height: int(height)}
|
||||
if err = C.clGetDeviceIDs(platform, C.CL_DEVICE_TYPE_GPU, 1, &g.dev, nil); err != C.CL_SUCCESS {
|
||||
return nil, fmt.Errorf("opencl: no suitable gpu device found")
|
||||
}
|
||||
|
||||
g.ctx = C.clCreateContext(nil, 1, &g.dev, nil, nil, &err)
|
||||
if err != C.CL_SUCCESS {
|
||||
return nil, fmt.Errorf("opencl: ctx creation error")
|
||||
}
|
||||
|
||||
g.queue = C.clCreateCommandQueueWithProperties(g.ctx, g.dev, nil, &err)
|
||||
if err != C.CL_SUCCESS {
|
||||
g.queue = C.clCreateCommandQueue(g.ctx, g.dev, 0, &err)
|
||||
}
|
||||
|
||||
src, errGo := os.ReadFile(kernelPath)
|
||||
if errGo != nil {
|
||||
g.Close()
|
||||
return nil, fmt.Errorf("opencl: read kernel error: %w", errGo)
|
||||
}
|
||||
|
||||
cSrc := C.CString(string(src))
|
||||
defer C.free(unsafe.Pointer(cSrc))
|
||||
|
||||
g.prog = C.build_cl_program(g.ctx, g.dev, cSrc, &err)
|
||||
if err != C.CL_SUCCESS {
|
||||
msg := "clCreateProgramWithSource failed"
|
||||
if g.prog != nil {
|
||||
msg = C.GoString(C.get_build_log(g.prog, g.dev))
|
||||
}
|
||||
g.Close()
|
||||
return nil, fmt.Errorf("opencl: compilation failed: %s", msg)
|
||||
}
|
||||
|
||||
cKName := C.CString("generate_v210_pattern")
|
||||
defer C.free(unsafe.Pointer(cKName))
|
||||
g.kernel = C.clCreateKernel(g.prog, cKName, &err)
|
||||
if err != C.CL_SUCCESS {
|
||||
g.Close()
|
||||
return nil, fmt.Errorf("opencl: kernel init failed")
|
||||
}
|
||||
|
||||
g.atlasBuf = C.clCreateBuffer(g.ctx, C.CL_MEM_READ_ONLY|C.CL_MEM_COPY_HOST_PTR,
|
||||
C.size_t(len(fontAtlas8x16)), unsafe.Pointer(&fontAtlas8x16[0]), &err)
|
||||
if err != C.CL_SUCCESS {
|
||||
g.Close()
|
||||
return nil, fmt.Errorf("opencl: font atlas upload failed: %d", err)
|
||||
}
|
||||
if err := g.setArg(4, C.sizeof_cl_mem, unsafe.Pointer(&g.atlasBuf)); err != nil {
|
||||
g.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Overlay defaults: no text, white on black if SetText is ever called.
|
||||
if err := g.setTextArgs(g.atlasBuf, TextConfig{
|
||||
Scale: 1,
|
||||
FG: [3]uint32{940, 512, 512},
|
||||
BG: [3]uint32{64, 512, 512},
|
||||
}); err != nil {
|
||||
g.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return g, nil
|
||||
}
|
||||
|
||||
func (g *OpenCLGenerator) GenerateFrame(dest []byte, frameIndex int) error {
|
||||
var err C.cl_int
|
||||
byteSize := C.size_t(len(dest))
|
||||
|
||||
// ВАЖНО: Мы маппим память MXL SDK ([]byte) напрямую в OpenCL буфер.
|
||||
// GPU запишет данные прямо в общую память MXL без лишнего копирования (Zero-Copy).
|
||||
if want := (g.width * g.height / 6) * 16; len(dest) < want {
|
||||
return fmt.Errorf("opencl: dest too small: %d bytes, need %d", len(dest), want)
|
||||
}
|
||||
clMem := C.clCreateBuffer(
|
||||
g.ctx,
|
||||
C.CL_MEM_WRITE_ONLY|C.CL_MEM_USE_HOST_PTR,
|
||||
byteSize,
|
||||
unsafe.Pointer(&dest[0]),
|
||||
&err,
|
||||
)
|
||||
if err != C.CL_SUCCESS {
|
||||
return fmt.Errorf("opencl: mxl host pointer mapping failed: %d", err)
|
||||
}
|
||||
defer C.clReleaseMemObject(clMem)
|
||||
|
||||
if err := g.setArg(0, C.sizeof_cl_mem, unsafe.Pointer(&clMem)); err != nil {
|
||||
return err
|
||||
}
|
||||
w, h, f := C.int(g.width), C.int(g.height), C.int(frameIndex)
|
||||
if err := g.setArg(1, C.sizeof_int, unsafe.Pointer(&w)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.setArg(2, C.sizeof_int, unsafe.Pointer(&h)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.setArg(3, C.sizeof_int, unsafe.Pointer(&f)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Каждый поток GPU пакует блок из 6 пикселей (16 байт)
|
||||
globalWorkSize := C.size_t((g.width * g.height) / 6)
|
||||
err = C.clEnqueueNDRangeKernel(g.queue, g.kernel, 1, nil, &globalWorkSize, nil, 0, nil, nil)
|
||||
if err != C.CL_SUCCESS {
|
||||
return fmt.Errorf("opencl: dispatch failed: %d", err)
|
||||
}
|
||||
|
||||
// Гарантированная синхронизация записи GPU в host-память: map/unmap.
|
||||
// На discrete GPU драйвер держит копию в device memory; одного clFinish
|
||||
// недостаточно — данные обязаны появиться в host_ptr только после unmap.
|
||||
mapped := C.clEnqueueMapBuffer(g.queue, clMem, C.CL_TRUE, C.CL_MAP_READ, 0, byteSize, 0, nil, nil, &err)
|
||||
if err != C.CL_SUCCESS {
|
||||
return fmt.Errorf("opencl: map failed: %d", err)
|
||||
}
|
||||
if err = C.clEnqueueUnmapMemObject(g.queue, clMem, mapped, 0, nil, nil); err != C.CL_SUCCESS {
|
||||
return fmt.Errorf("opencl: unmap failed: %d", err)
|
||||
}
|
||||
if err = C.clFinish(g.queue); err != C.CL_SUCCESS {
|
||||
return fmt.Errorf("opencl: finish failed: %d", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *OpenCLGenerator) Close() error {
|
||||
if g.textBuf != nil {
|
||||
C.clReleaseMemObject(g.textBuf)
|
||||
g.textBuf = nil
|
||||
}
|
||||
if g.atlasBuf != nil {
|
||||
C.clReleaseMemObject(g.atlasBuf)
|
||||
g.atlasBuf = nil
|
||||
}
|
||||
if g.kernel != nil {
|
||||
C.clReleaseKernel(g.kernel)
|
||||
g.kernel = nil
|
||||
}
|
||||
if g.prog != nil {
|
||||
C.clReleaseProgram(g.prog)
|
||||
g.prog = nil
|
||||
}
|
||||
if g.queue != nil {
|
||||
C.clReleaseCommandQueue(g.queue)
|
||||
g.queue = nil
|
||||
}
|
||||
if g.ctx != nil {
|
||||
C.clReleaseContext(g.ctx)
|
||||
g.ctx = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user