Search code examples
c#.net-corecross-platformopentkimagesharp

Loaded texture using ImageSharp displays white (opentk)


After loading the image with the help of ImageSharp and then loading it into vram it renders just a white quad. I basicly have no clue what i could have done wrong, this is the first time working with opentk crossplatform not beeing abel to use BitmapData.

Texture class:

public struct Texture2D
{
    public readonly int Handle;
    public readonly int Width;
    public readonly int Height;

    public Texture2D(int[] data, int width, int height)
    {
        Width = width;
        Height = height;

        GL.Enable(EnableCap.Texture2D);
        GL.Hint(HintTarget.PerspectiveCorrectionHint, HintMode.Nicest);
        Handle = GL.GenTexture();
        GL.BindTexture(TextureTarget.Texture2D, Handle);

        GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
        GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
        GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat);
        GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat);

        unsafe
        {
            fixed (int* dataptr = data)
                GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, Width, Height, 0, PixelFormat.Bgra, PixelType.UnsignedByte, (IntPtr)dataptr);
            GL.BindTexture(TextureTarget.Texture2D, 0);
        }

        GL.Disable(EnableCap.Texture2D);
    }

    public static implicit operator int(Texture2D texture) => texture.Handle;

    public static Texture2D FromFile(string file) => FromImage(Image.Load(Path.Combine("resources", file)));

    public static Texture2D FromImage(Image<Rgba32> image)
    {
        var xL = image.Width;
        var yL = image.Height;
        var data = new int[image.Width * image.Height];
        for (var x = 0; x < xL; x++)
            for (var y = 0; y < yL; y++)
                data[y * xL + x] = image[x, y].R << 24 | image[x, y].G << 16 | image[x, y].B << 8 | image[x, y].A;
        return new Texture2D(data, image.Width, image.Height);
    }
}

Rendering:

GL.Clear(ClearBufferMask.ColorBufferBit);
GL.PushMatrix();
GL.Begin(PrimitiveType.Quads);
{
    GL.BindTexture(TextureTarget.ProxyTexture2D, loadingTex.Handle);

    var x = app.Width - loadingTex.Width;
    var x2 = app.Width;
    var y = 0;
    var y2 = loadingTex.Height;

    GL.TexCoord2(0, 0);
    GL.Vertex2(x, 0);

    GL.TexCoord2(0, 1);
    GL.Vertex2(x2, 0);

    GL.TexCoord2(1, 1);
    GL.Vertex2(x2, y2);

    GL.TexCoord2(1, 0);
    GL.Vertex2(x, loadingTex.Height);
}
GL.End();
GL.PopMatrix();
GL.Flush();
SwapBuffers();

loadingTex is a static variable referencing a Texture


Solution

  • This is .net core only, it works for .net framework, but there are better solution.

    After some lots of research, I found my answer.

    The short answer: GL.TexImage2D accepts an array of all the color in you image.

    The long answer:

    1. Getting such array from an image.

    Well, actually that's quite easy. But this requires you to use the ImageSharp library from SixLabors, but if you find a library the works the similar way feel free to use that.

    The first step is, load the image into as Image<Rgba32>. Then you need to put the image data into a 1D (int) array. Each int should be structured like this ABGR.

    Example:

    var xL = image.Width;
    var yL = image.Height;
    var data = new int[image.Width * image.Height];
    for (var x = 0; x < xL; x++)
        for (var y = 0; y < yL; y++)
        {
            var color = image[x, y];
            data[y * xL + x] = (color.A << 24) | (color.B << 16) | (color.G << 8) | (color.R << 0);
        }
    

    This code loops trough every pixel of the image and writes it into the int array.

    1. Loading it into memory (using unsafe code) (This only covers GL.TexImage2D)

    Now we need to get a pointer to that array by using the fixed statement

    fixed (int* dataptr = data)
    

    This statement needs a body!

    GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, Width, Height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, (IntPtr) dataptr);
    

    And this one loads it into the VRam. Lets skip the first 2 arguments i won't need to explain them for this problem. The third argument PixelInternalFormat.Rgba tells the gpu how to store each pixel into the V-Ram. The next 2 are telling the gpu the dimensions of the image. Argument 6 is not important for now. The next argument PixelFormat.Rgba tell the gpu in what format you're passing the pixels. Now we have PixelType.UnsignedByte what tells the gpu how big one part of the pixel is (Red, Blue, Green and Alpha are all parts of the pixel), not the size of the pixel it self. And finally passing the data it self as IntPtr what basicly is a universal pointer that can be passed to a native library.