-
SummaryI'm porting C++ code to Silk and found that my custom Shader class, when I do Shader.Use(), was reporting Steps to reproduce
var errCode = _gl.GetError();
if (errCode != GLEnum.NoError)
{
Console.WriteLine($"Before Uniform1(): {errCode}");
_window.IsClosing = true;
return;
}
errCode = _gl.GetError();
if (errCode != GLEnum.NoError)
{
Console.WriteLine($"After Uniform1(): {errCode}");
_window.IsClosing = true;
return;
}
CommentsI'm not sure what causes the problem. |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments
-
I ported my custom OGLTexture2D class from C++, that is using builder pattern under the hood, and I get the same problem, Once again, ignoring error, just like with original code, produces proper result. But the error is still there. |
Beta Was this translation helpful? Give feedback.
-
I found the problem. Those two lines from tutorial: int location = _gl.GetUniformLocation(_program, "uTexture");
_gl.Uniform1(location, 0); should be moved from Then checking for error after |
Beta Was this translation helpful? Give feedback.
-
Here is fully modified code of the tutorial, that properly passes error checking: using System;
using System.Drawing;
using System.IO;
using Silk.NET.Maths;
using Silk.NET.Windowing;
using Silk.NET.OpenGL;
using StbImageSharp;
// Textures!
// In this tutorial, you'll learn how to load and render textures.
namespace Tutorial
{
public class Program
{
private static IWindow _window;
private static GL _gl;
private static uint _vao;
private static uint _vbo;
private static uint _ebo;
private static uint _program;
private static uint _texture;
public static void Main(string[] args)
{
WindowOptions options = WindowOptions.Default;
options.Size = new Vector2D<int>(800, 600);
options.Title = "1.3 - Textures";
_window = Window.Create(options);
_window.Load += OnLoad;
_window.Update += OnUpdate;
_window.Render += OnRender;
_window.FramebufferResize += OnFramebufferResize;
_window.Run();
_window.Dispose();
}
private static unsafe void OnLoad()
{
_gl = _window.CreateOpenGL();
_gl.ClearColor(Color.CornflowerBlue);
// Create the VAO.
_vao = _gl.GenVertexArray();
_gl.BindVertexArray(_vao);
// The quad vertices data.
// You may have noticed an addition - texture coordinates!
// Texture coordinates are a value between 0-1 (see more later about this) which tell the GPU which part
// of the texture to use for each vertex.
float[] vertices =
{
// aPosition-------- aTexCoords
0.5f, 0.5f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.0f, 0.0f, 1.0f
};
// Create the VBO.
_vbo = _gl.GenBuffer();
_gl.BindBuffer(BufferTargetARB.ArrayBuffer, _vbo);
// Upload the vertices data to the VBO.
fixed (float* buf = vertices)
_gl.BufferData(BufferTargetARB.ArrayBuffer, (nuint)(vertices.Length * sizeof(float)), buf, BufferUsageARB.StaticDraw);
// The quad indices data.
uint[] indices =
{
0u, 1u, 3u,
1u, 2u, 3u
};
// Create the EBO.
_ebo = _gl.GenBuffer();
_gl.BindBuffer(BufferTargetARB.ElementArrayBuffer, _ebo);
// Upload the indices data to the EBO.
fixed (uint* buf = indices)
_gl.BufferData(BufferTargetARB.ElementArrayBuffer, (nuint)(indices.Length * sizeof(uint)), buf, BufferUsageARB.StaticDraw);
// The vertex shader code.
const string vertexCode = @"
#version 330 core
layout (location = 0) in vec3 aPosition;
// On top of our aPosition attribute, we now create an aTexCoords attribute for our texture coordinates.
layout (location = 1) in vec2 aTexCoords;
// Likewise, we also assign an out attribute to go into the fragment shader.
out vec2 frag_texCoords;
void main()
{
gl_Position = vec4(aPosition, 1.0);
// This basic vertex shader does no additional processing of texture coordinates, so we can pass them
// straight to the fragment shader.
frag_texCoords = aTexCoords;
}";
// The fragment shader code.
const string fragmentCode = @"
#version 330 core
// This in attribute corresponds to the out attribute we defined in the vertex shader.
in vec2 frag_texCoords;
out vec4 out_color;
// Now we define a uniform value!
// A uniform in OpenGL is a value that can be changed outside of the shader by modifying its value.
// A sampler2D contains both a texture and information on how to sample it.
// Sampling a texture is basically calculating the color of a pixel on a texture at any given point.
uniform sampler2D uTexture;
void main()
{
// We use GLSL's texture function to sample from the texture at the given input texture coordinates.
out_color = texture(uTexture, frag_texCoords);
}";
// Create our vertex shader, and give it our vertex shader source code.
uint vertexShader = _gl.CreateShader(ShaderType.VertexShader);
_gl.ShaderSource(vertexShader, vertexCode);
// Attempt to compile the shader.
_gl.CompileShader(vertexShader);
// Check to make sure that the shader has successfully compiled.
_gl.GetShader(vertexShader, ShaderParameterName.CompileStatus, out int vStatus);
if (vStatus != (int)GLEnum.True)
throw new Exception("Vertex shader failed to compile: " + _gl.GetShaderInfoLog(vertexShader));
// Repeat this process for the fragment shader.
uint fragmentShader = _gl.CreateShader(ShaderType.FragmentShader);
_gl.ShaderSource(fragmentShader, fragmentCode);
_gl.CompileShader(fragmentShader);
_gl.GetShader(fragmentShader, ShaderParameterName.CompileStatus, out int fStatus);
if (fStatus != (int)GLEnum.True)
throw new Exception("Fragment shader failed to compile: " + _gl.GetShaderInfoLog(fragmentShader));
// Create our shader program, and attach the vertex & fragment shaders.
_program = _gl.CreateProgram();
_gl.AttachShader(_program, vertexShader);
_gl.AttachShader(_program, fragmentShader);
// Attempt to "link" the program together.
_gl.LinkProgram(_program);
// Similar to shader compilation, check to make sure that the shader program has linked properly.
_gl.GetProgram(_program, ProgramPropertyARB.LinkStatus, out int lStatus);
if (lStatus != (int)GLEnum.True)
throw new Exception("Program failed to link: " + _gl.GetProgramInfoLog(_program));
// Detach and delete our shaders. Once a program is linked, we no longer need the individual shader objects.
_gl.DetachShader(_program, vertexShader);
_gl.DetachShader(_program, fragmentShader);
_gl.DeleteShader(vertexShader);
_gl.DeleteShader(fragmentShader);
// Set up our vertex attributes! These tell the vertex array (VAO) how to process the vertex data we defined
// earlier. Each vertex array contains attributes.
// Our stride constant. The stride must be in bytes, so we take the first attribute (a vec3), multiply it
// by the size in bytes of a float, and then take our second attribute (a vec2), and do the same.
const uint stride = (3 * sizeof(float)) + (2 * sizeof(float));
// Enable the "aPosition" attribute in our vertex array, providing its size and stride too.
const uint positionLoc = 0;
_gl.EnableVertexAttribArray(positionLoc);
_gl.VertexAttribPointer(positionLoc, 3, VertexAttribPointerType.Float, false, stride, (void*)0);
// Now we need to enable our texture coordinates! We've defined that as location 1 so that's what we'll use
// here. The code is very similar to above, but you must make sure you set its offset to the **size in bytes**
// of the attribute before.
const uint textureLoc = 1;
_gl.EnableVertexAttribArray(textureLoc);
_gl.VertexAttribPointer(textureLoc, 2, VertexAttribPointerType.Float, false, stride, (void*)(3 * sizeof(float)));
// Unbind everything as we don't need it.
_gl.BindVertexArray(0);
_gl.BindBuffer(BufferTargetARB.ArrayBuffer, 0);
_gl.BindBuffer(BufferTargetARB.ElementArrayBuffer, 0);
// Now we create our texture!
// First, we create the texture itself. Then, we must set an active texture unit. Each texture unit is a
// separate bindable texture that we can use in a shader. GPUs have a maximum number of texture units they
// can use, however the OpenGL spec states there MUST be at least 32 units available.
// Much like buffers, we then bind the texture to a Texture2D target.
_texture = _gl.GenTexture();
_gl.ActiveTexture(TextureUnit.Texture0);
_gl.BindTexture(TextureTarget.Texture2D, _texture);
// Use StbImageSharp to load an image from our PNG file.
// This will load and decompress the result into a raw byte array that we can pass directly into OpenGL.
ImageResult result = ImageResult.FromMemory(File.ReadAllBytes("silk.png"), ColorComponents.RedGreenBlueAlpha);
fixed (byte* ptr = result.Data)
{
// Upload our texture data to the GPU.
// Let's go over each parameter used here:
// 1. Tell OpenGL that we want to upload to the texture bound in the Texture2D target.
// 2. We are uploading the "base" texture level, therefore this value should be 0. You don't need to
// worry about texture levels for now.
// 3. We tell OpenGL that we want the GPU to store this data as RGBA formatted data on the GPU itself.
// 4. The image's width.
// 5. The image's height.
// 6. This is the image's border. This valu MUST be 0. It is a leftover component from legacy OpenGL, and
// it serves no purpose.
// 7. Our image data is formatted as RGBA data, therefore we must tell OpenGL we are uploading RGBA data.
// 8. StbImageSharp returns this data as a byte[] array, therefore we must tell OpenGL we are uploading
// data in the unsigned byte format.
// 9. The actual pointer to our data!
_gl.TexImage2D(TextureTarget.Texture2D, 0, InternalFormat.Rgba, (uint)result.Width,
(uint)result.Height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, ptr);
}
// Let's set some texture parameters!
// This tells the GPU how it should sample the texture.
// Set the texture wrap mode to repeat.
// The texture wrap mode defines what should happen when the texture coordinates go outside of the 0-1 range.
// In this case, we set it to repeat. The texture will just repeatedly tile over and over again.
// You'll notice we're using S and T wrapping here. This is OpenGL's version of the standard UV mapping you
// may be more used to, where S is on the X-axis, and T is on the Y-axis.
_gl.TextureParameter(_texture, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat);
_gl.TextureParameter(_texture, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat);
// The min and mag filters define how the texture should be sampled as it resized.
// The min, or minification filter, is used when the texture is reduced in size.
// The mag, or magnification filter, is used when the texture is increased in size.
// We're using bilinear filtering here, as it produces a generally nice result.
// You can also use nearest (point) filtering, or anisotropic filtering, which is only available on the min
// filter.
// You may notice that the min filter defines a "mipmap" filter as well. We'll go over mipmaps below.
_gl.TextureParameter(_texture, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.LinearMipmapLinear);
_gl.TextureParameter(_texture, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
// Generate mipmaps for this texture.
// Note: We MUST do this or the texture will appear as black (this is an option you can change but this is
// out of scope for this tutorial).
// What is a mipmap?
// A mipmap is essentially a smaller version of the existing texture. When generating mipmaps, the texture
// size is continuously halved, generally stopping once it reaches a size of 1x1 pixels. (Note: there are
// exceptions to this, for example if the GPU reaches its maximum level of mipmaps, which is both a hardware
// limitation, and a user defined value. You don't need to worry about this for now, so just assume that
// the mips will be generated all the way down to 1x1 pixels).
// Mipmaps are used when the texture is reduced in size, to produce a much nicer result, and to reduce moire
// effect patterns.
_gl.GenerateMipmap(TextureTarget.Texture2D);
// Unbind the texture as we no longer need to update it any further.
_gl.BindTexture(TextureTarget.Texture2D, 0);
// Finally a bit of blending!
// If you disable blending, you'll notice a black border around the texture.
// The texture is partially transparent, however OpenGL doesn't know how to handle this by default.
// By enabling blending, and giving it a blend function, you can tell OpenGL how to handle transparency.
// In this case, it removes the black background and just leaves the texture on its own.
// The blend function is out of scope for this tutorial, so don't worry if you don't understand it too much.
// The program will function just fine without blending!
_gl.Enable(EnableCap.Blend);
_gl.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
}
private static void OnUpdate(double dt) { }
private static unsafe void OnRender(double dt)
{
// Clear the window to the color we set earlier.
_gl.Clear(ClearBufferMask.ColorBufferBit);
// Bind our VAO, then the program.
_gl.BindVertexArray(_vao);
_gl.UseProgram(_program);
// Get our texture uniform, and set it to 0.
// We can easily do this by using glGetUniformLocation and giving it a name.
// Setting it to 0 tells it that you want it to use the 0th texture unit.
// Generally, OpenGL should automatically initialize all uniform values to their default value (which is
// almost always 0), however you should get into the practice of initializing all uniform values to a known
// value, before you use them in your shader.
int location = _gl.GetUniformLocation(_program, "uTexture");
_gl.Uniform1(location, 0); // Setting uniform should be performed on active program, that's why we perform it after the call to UseProgram()
var err = _gl.GetError();
if (err != GLEnum.NoError)
{
Console.WriteLine(err);
_window.IsClosing = true;
return;
}
// Much like our texture creation earlier, we must first set our active texture unit, and then bind the
// texture to use it during draw!
_gl.ActiveTexture(TextureUnit.Texture0);
_gl.BindTexture(TextureTarget.Texture2D, _texture);
// Draw our quad! We use a count of 6 here because we have 6 total vertices that makes up a quad.
_gl.DrawElements(PrimitiveType.Triangles, 6, DrawElementsType.UnsignedInt, (void*)0);
}
private static void OnFramebufferResize(Vector2D<int> newSize)
{
_gl.Viewport(newSize);
}
}
} |
Beta Was this translation helpful? Give feedback.
Here is fully modified code of the tutorial, that properly passes error checking: