veandco / go-sdl2

SDL2 binding for Go
https://godoc.org/github.com/veandco/go-sdl2
BSD 3-Clause "New" or "Revised" License
2.17k stars 219 forks source link

Upscale surface & texture not displaying correctly #556

Closed ryanbeau closed 1 year ago

ryanbeau commented 1 year ago

Go version: 1.19 Go-SDL2 version: v0.4.33 SDL2 version: 2.0.20 OS: Windows 10 & Ubuntu Ubuntu 22.04.1 LTS (I tried both)

In an attempt to copy SDL2 setup & functionality from: Chocolate-Doom - i_video.c

What it does: It attempts to keep the original resolution of Doom (320x200) and upscales it to fit the window or screen. It has a 320*200 screenBuffer Surface with a 256 color palette. The surface does a LowerBlit to an ARGB destination Surface. It then updates the low scale Texture from the pixels of the ARGB Surface, and then copies the low scale texture to the upscaled texture, and draws that to the screen.

I'm unsure if I've done something different or incorrectly. Most specifically I'm unsure about the unsafe.Pointer to the ARGB Pixels.

In C, the results are as expected: image

Expand to view C source ```c #include #include #include #include #define SCREENWIDTH 320 #define SCREENHEIGHT 200 static SDL_Rect blit_rect = {0, 0, SCREENWIDTH, SCREENHEIGHT}; static SDL_Color palette[256]; unsigned char *I_VideoBuffer = NULL; void I_SetPalette() { int i; for (i = 0; i < 85; i++) { palette[i].r = (Uint8)i*3 & ~3; palette[i+85].g = (Uint8)i*3 & ~3; palette[i+170].b = (Uint8)i*3 & ~3; } } int main(int argc, char *argv[]) { SDL_Window *screen; SDL_Renderer *renderer; SDL_Surface *screenbuffer = NULL; SDL_Surface *argbbuffer = NULL; SDL_Texture *texture = NULL; SDL_Texture *texture_upscaled = NULL; uint32_t pixel_format; int bpp; unsigned int rmask, gmask, bmask, amask; SDL_Event dummy; SDL_Init(SDL_INIT_VIDEO); screen = SDL_CreateWindow(NULL, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, SCREENWIDTH*2, SCREENHEIGHT*2, SDL_WINDOW_RESIZABLE|SDL_WINDOW_ALLOW_HIGHDPI); SDL_SetWindowMinimumSize(screen, SCREENWIDTH, SCREENHEIGHT); renderer = SDL_CreateRenderer(screen, -1, SDL_RENDERER_TARGETTEXTURE); SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); SDL_RenderClear(renderer); SDL_RenderPresent(renderer); screenbuffer = SDL_CreateRGBSurface(0, SCREENWIDTH, SCREENHEIGHT, 8, 0, 0, 0, 0); SDL_FillRect(screenbuffer, NULL, 0); pixel_format = SDL_GetWindowPixelFormat(screen); SDL_PixelFormatEnumToMasks(pixel_format, &bpp, &rmask, &gmask, &bmask, &amask); argbbuffer = SDL_CreateRGBSurface(0, SCREENWIDTH, SCREENHEIGHT, bpp, rmask, gmask, bmask, amask); SDL_FillRect(argbbuffer, NULL, 0); SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "nearest"); texture = SDL_CreateTexture(renderer, pixel_format, SDL_TEXTUREACCESS_STREAMING, SCREENWIDTH, SCREENHEIGHT); SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear"); texture_upscaled = SDL_CreateTexture(renderer, pixel_format, SDL_TEXTUREACCESS_TARGET, SCREENWIDTH*2, SCREENHEIGHT*2); SDL_FillRect(screenbuffer, NULL, 0); I_SetPalette(); SDL_SetPaletteColors(screenbuffer->format->palette, palette, 0, 256); I_VideoBuffer = screenbuffer->pixels; memset(I_VideoBuffer, 0, SCREENWIDTH * SCREENHEIGHT * sizeof(*I_VideoBuffer)); while (SDL_PollEvent(&dummy)) ; int i, j, k; for (int i = 0; i < 256; i++) { //draw lines to screen for (j = 0; j < SCREENHEIGHT; j++) { for (k = 0; k < SCREENWIDTH; k++) { I_VideoBuffer[j*SCREENWIDTH+k] = (unsigned char)i+j; } } // Blit from the paletted 8-bit screen buffer to the intermediate // 32-bit RGBA buffer that we can load into the texture. SDL_LowerBlit(screenbuffer, &blit_rect, argbbuffer, &blit_rect); // Update the intermediate texture with the contents of the RGBA buffer. SDL_UpdateTexture(texture, NULL, argbbuffer->pixels, argbbuffer->pitch); // Make sure the pillarboxes are kept clear each frame. SDL_RenderClear(renderer); // Render this intermediate texture into the upscaled texture // using "nearest" integer scaling. SDL_SetRenderTarget(renderer, texture_upscaled); SDL_RenderCopy(renderer, texture, NULL, NULL); // Finally, render this upscaled texture to screen using linear scaling. SDL_SetRenderTarget(renderer, NULL); SDL_RenderCopy(renderer, texture_upscaled, NULL, NULL); // Draw! SDL_RenderPresent(renderer); SDL_Delay(10); } SDL_DestroyTexture(texture_upscaled); SDL_DestroyTexture(texture); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(screen); SDL_Quit(); } ```

In Go, the results are quite random: image

package main

import (
    "fmt"
    "os"
    "unsafe"

    "github.com/veandco/go-sdl2/sdl"
)

const SCREENWIDTH = 320
const SCREENHEIGHT = 200

var blit_rect = sdl.Rect{X: 0, Y: 0, W: SCREENWIDTH, H: SCREENHEIGHT}
var pixel_format uint32
var palette = [256]sdl.Color{}
var I_VideoBuffer []byte

func I_SetPalette() {
    for i := 0; i < 85; i++ {
        palette[i].R = byte(i * 3 & ^3)
        palette[i+85].G = byte(i * 3 & ^3)
        palette[i+170].B = byte(i * 3 & ^3)
    }
}

func main() {
    var dummy sdl.Event

    if err := sdl.Init(sdl.INIT_VIDEO); err != nil {
        fmt.Fprintf(os.Stderr, "SDL_Init Error: %s\n", err.Error())
        return
    }

    screen, err := sdl.CreateWindow("",
        sdl.WINDOWPOS_CENTERED, sdl.WINDOWPOS_CENTERED, SCREENWIDTH*2, SCREENHEIGHT*2,
        sdl.WINDOW_RESIZABLE|sdl.WINDOW_ALLOW_HIGHDPI)
    if err != nil {
        fmt.Fprintf(os.Stderr, "SDL_CreateWindow Error: %s\n", err.Error())
        return
    }
    defer screen.Destroy()

    screen.SetMinimumSize(SCREENWIDTH, SCREENHEIGHT)

    renderer, err := sdl.CreateRenderer(screen, -1, sdl.RENDERER_TARGETTEXTURE)
    if renderer == nil {
        fmt.Fprintf(os.Stderr, "SDL_CreateRenderer Error: %s\n", err.Error())
        return
    }
    defer renderer.Destroy()

    renderer.SetDrawColor(0, 0, 0, 255)
    renderer.Clear()
    renderer.Present()

    screenbuffer, err := sdl.CreateRGBSurface(0, SCREENWIDTH, SCREENHEIGHT, 8, 0, 0, 0, 0)
    if err != nil {
        fmt.Fprintf(os.Stderr, "SDL_CreateRGBSurface Error: %s\n", err.Error())
        return
    }
    defer screenbuffer.Free()

    screenbuffer.FillRect(nil, 0)

    pixel_format, err = screen.GetPixelFormat()
    if err != nil {
        fmt.Fprintf(os.Stderr, "SDL_GetWindowPixelFormat Error: %s\n", err.Error())
        return
    }

    bpp, rmask, gmask, bmask, amask, err := sdl.PixelFormatEnumToMasks(uint(pixel_format))
    if err != nil {
        fmt.Fprintf(os.Stderr, "SDL_PixelFormatEnumToMasks Error: %s\n", err.Error())
        return
    }
    argbbuffer, err := sdl.CreateRGBSurface(0, SCREENWIDTH, SCREENHEIGHT, int32(bpp), rmask, gmask, bmask, amask)
    if err != nil {
        fmt.Fprintf(os.Stderr, "SDL_CreateRGBSurface Error: %s\n", err.Error())
        return
    }
    defer argbbuffer.Free()

    argbbuffer.FillRect(nil, 0)

    sdl.SetHint(sdl.HINT_RENDER_SCALE_QUALITY, "nearest")

    texture, err := renderer.CreateTexture(pixel_format, sdl.TEXTUREACCESS_STREAMING, SCREENWIDTH, SCREENHEIGHT)
    if err != nil {
        fmt.Fprintf(os.Stderr, "SDL_CreateTexture Error: %s\n", err.Error())
        return
    }
    defer texture.Destroy()

    sdl.SetHint(sdl.HINT_RENDER_SCALE_QUALITY, "linear")

    texture_upscaled, err := renderer.CreateTexture(pixel_format, sdl.TEXTUREACCESS_TARGET, SCREENWIDTH*2, SCREENHEIGHT*2)
    if err != nil {
        fmt.Fprintf(os.Stderr, "SDL_CreateTexture Error: %s\n", err.Error())
        return
    }

    screenbuffer.FillRect(nil, 0)

    I_SetPalette()

    screenbuffer.Format.Palette.SetColors(palette[:])

    I_VideoBuffer = screenbuffer.Pixels()

    for i := 0; i < SCREENWIDTH*SCREENHEIGHT; i++ {
        I_VideoBuffer[i] = 0
    }

    for dummy = sdl.PollEvent(); dummy != nil; dummy = sdl.PollEvent() {
    }

    for i := 0; i < 256; i++ {
        //draw lines to screen
        for j := 0; j < SCREENHEIGHT; j++ {
            for k := 0; k < SCREENWIDTH; k++ {
                I_VideoBuffer[j*SCREENWIDTH+k] = byte(i + j)
            }
        }

        // Blit from the paletted 8-bit screen buffer to the intermediate
        // 32-bit RGBA buffer that we can load into the texture.
        screenbuffer.LowerBlit(&blit_rect, argbbuffer, &blit_rect)

        // Update the intermediate texture with the contents of the RGBA buffer.
        pixels := argbbuffer.Pixels()
        texture.Update(nil, unsafe.Pointer(&pixels), int(argbbuffer.Pitch))

        // Make sure the pillarboxes are kept clear each frame.
        renderer.Clear()

        // Render this intermediate texture into the upscaled texture
        // using "nearest" integer scaling.
        renderer.SetRenderTarget(texture_upscaled)
        renderer.Copy(texture, nil, nil)

        // Finally, render this upscaled texture to screen using linear scaling.
        renderer.SetRenderTarget(nil)
        renderer.Copy(texture_upscaled, nil, nil)

        // Draw!
        renderer.Present()

        sdl.Delay(10)
    }

    sdl.Quit()
}

Thanks

ryanbeau commented 1 year ago

Oh my... I just discovered that this works in Go:

// Update the intermediate texture with the contents of the RGBA buffer.
pixels := argbbuffer.Pixels()
tpixels, _, err := texture.Lock(nil)
if err != nil {
    panic("oops")
}
for i := 0; i < len(tpixels); i++ {
    tpixels[i] = pixels[i]
}
texture.Unlock()

And it's the same as this:

// Update the intermediate texture with the contents of the RGBA buffer.
SDL_UpdateTexture(texture, NULL, argbbuffer->pixels, argbbuffer->pitch);

I suppose this thread could be closed, unless the above behavior is unexpected considering it works in C. Thanks

veeableful commented 1 year ago

Hi @ryanbeau, it seems to be caused by the pixel data address being the address of the pixels slice &pixels rather than its data &pixels[0] when passed to texture.Update() function.

ryanbeau commented 1 year ago

That makes sense & works. Thanks