diff --git a/examples/Playground/Program.fs b/examples/Playground/Program.fs index a13e008..1ea0943 100644 --- a/examples/Playground/Program.fs +++ b/examples/Playground/Program.fs @@ -26,13 +26,19 @@ type GameState = Triangles = Primitives.ShadedObject.Default } let private initHandler (config:Config) = - let fragmentPaths = ["Resources/Shaders/fragment.glsl"] - let vertexPaths = ["Resources/Shaders/vertex.glsl"] + let fragmentPaths = ["Resources/Shaders/Texture/fragment.glsl"] + let vertexPaths = ["Resources/Shaders/Texture/vertex.glsl"] let transform = { Transform.Default() with Scale = (1.0f, 1.0f, 1.0f) Rotation = (0.0f, 0.0f, 0.0f) } - match Primitives.ShadedObject.CreateQuad vertexPaths fragmentPaths transform 1.8f 1.0f with + match Primitives.ShadedObject.CreateQuad + vertexPaths + fragmentPaths + transform + 1.8f + 1.0f + (Types.Texture2D.FromPath "Resources/Textures/smiley.png" |> Some) with | Some primitive -> { config with State.Triangles = primitive } diff --git a/examples/Playground/Resources/Shaders/fragment.glsl b/examples/Playground/Resources/Shaders/Scanlines/fragment.glsl similarity index 100% rename from examples/Playground/Resources/Shaders/fragment.glsl rename to examples/Playground/Resources/Shaders/Scanlines/fragment.glsl diff --git a/examples/Playground/Resources/Shaders/vertex.glsl b/examples/Playground/Resources/Shaders/Scanlines/vertex.glsl similarity index 83% rename from examples/Playground/Resources/Shaders/vertex.glsl rename to examples/Playground/Resources/Shaders/Scanlines/vertex.glsl index 97a4a07..cce78fa 100644 --- a/examples/Playground/Resources/Shaders/vertex.glsl +++ b/examples/Playground/Resources/Shaders/Scanlines/vertex.glsl @@ -1,6 +1,6 @@ #version 330 core -layout (location = 0) in vec3 in_position; // the in_position variable has attribute position 0 +layout (location = 0) in vec3 in_position; // the in_position variable has attribute position 0\ void main() { diff --git a/examples/Playground/Resources/Shaders/Texture/fragment.glsl b/examples/Playground/Resources/Shaders/Texture/fragment.glsl new file mode 100644 index 0000000..8c52f16 --- /dev/null +++ b/examples/Playground/Resources/Shaders/Texture/fragment.glsl @@ -0,0 +1,19 @@ +#version 330 core + +in vec2 out_texture_coordinate; + +out vec4 out_frag_color; + +uniform vec2 in_viewport; +uniform sampler2D in_texture; + +void main() +{ + // out_frag_color = texture(in_texture, TexCoord) + out_frag_color = vec4( + 1.0f - out_texture_coordinate.x, + 1.0f - out_texture_coordinate.y, + 0.0f, + 1.0f + ); +} diff --git a/examples/Playground/Resources/Shaders/Texture/vertex.glsl b/examples/Playground/Resources/Shaders/Texture/vertex.glsl new file mode 100644 index 0000000..4cea338 --- /dev/null +++ b/examples/Playground/Resources/Shaders/Texture/vertex.glsl @@ -0,0 +1,12 @@ +#version 330 core + +layout (location = 0) in vec3 in_position; // the in_position variable has attribute position 0 +layout (location = 1) in vec2 in_tex_coordinate; // the in_tex_coordinate variable has attribute position 1 + +out vec2 out_texture_coordinate; + +void main() +{ + gl_Position = vec4(in_position.x, in_position.y, in_position.z, 1.0); + out_texture_coordinate = in_tex_coordinate; +} diff --git a/examples/Playground/Resources/Textures/smiley.png b/examples/Playground/Resources/Textures/smiley.png new file mode 100644 index 0000000..65f9fc6 Binary files /dev/null and b/examples/Playground/Resources/Textures/smiley.png differ diff --git a/issues/in-progress.md b/issues/in-progress.md index 0be7fea..d83a1b8 100644 --- a/issues/in-progress.md +++ b/issues/in-progress.md @@ -6,7 +6,7 @@ Once completed, move the issue into [closed](./closed.md), and also close it wit ### [Issue Link](https://github.com/ExoKomodo/openwomb/issues/3) -### Branch - [3_sprites](https://github.com/exokomodo/openwomb/tree/3_sprites) +### [Branch](https://github.com/exokomodo/openwomb/tree/3_textures) ### Overview @@ -20,10 +20,11 @@ This issue is to only implement the OpenGL way, as it provides a general-purpose ### Instructions 1. Read the [OpenGL tutorial for rendering textures to a quad](https://learnopengl.com/Getting-started/Textures). Now prepare for implementation. -1. Will require some changes to the [Primitives.ShadedObject.From function](./src/Womb/Graphics/Primitives.fs) -1. Will require some changes to the [Primitives.ShadedObject.UseMvpShader function](./src/Womb/Graphics/Primitives.fs) -1. Will require some changes to the [example shaders in the Playground example](./examples/Playground/Resources/Shaders/) +1. Will require implementation of [glGenTextures in OpenGL1.1 API](../src/Womb/Backends/OpenGL/Api/OpenGL1_1.fs). Copy the pattern used with [glGenBuffers in OpenGL1.5 API](../src/Womb/Backends/OpenGL/Api/OpenGL1_5.fs), providing a helper for condition that `n=1`. +1. Will require some changes to the [Primitives.ShadedObject.From function](../src/Womb/Graphics/Primitives.fs) +1. Will require some changes to the [Primitives.ShadedObject.UseMvpShader function](../src/Womb/Graphics/Primitives.fs) +1. Will require some changes to the [example shaders in the Playground example](../examples/Playground/Resources/Shaders/) ### Acceptance Criteria -Be able to render an image to a shaded quad. To demonstrate this, modify the [Playground example](./examples/Playground) to render the [hello_world.bmp](./examples/Playground/Resources/Textures/hello_world.bmp). For proper completeness, demonstrate how we can color the texture in the shader as well. +Be able to render an image to a shaded quad. To demonstrate this, modify the [Playground example](../examples/Playground) to render the [hello_world.bmp](../examples/Playground/Resources/Textures/hello_world.bmp). For proper completeness, demonstrate how we can color the texture in the shader as well. diff --git a/src/ThirdParty/SDL2/Image.cs b/src/ThirdParty/SDL2/Image.cs index bf22cf8..a497176 100644 --- a/src/ThirdParty/SDL2/Image.cs +++ b/src/ThirdParty/SDL2/Image.cs @@ -89,13 +89,13 @@ public static SDL.SDL_version IMG_Linked_Version() /* IntPtr refers to an SDL_Surface* */ [DllImport(nativeLibName, EntryPoint = "IMG_Load", CallingConvention = CallingConvention.Cdecl)] - private static extern unsafe IntPtr INTERNAL_IMG_Load( + private static extern unsafe SDL.SDL_Surface* INTERNAL_IMG_Load( byte* file ); - public static unsafe IntPtr IMG_Load(string file) + public static unsafe SDL.SDL_Surface* IMG_Load(string file) { byte* utf8File = SDL.Utf8EncodeHeap(file); - IntPtr handle = INTERNAL_IMG_Load( + SDL.SDL_Surface* handle = INTERNAL_IMG_Load( utf8File ); Marshal.FreeHGlobal((IntPtr)utf8File); diff --git a/src/ThirdParty/SDL2/SDL2Bindings.cs b/src/ThirdParty/SDL2/SDL2Bindings.cs index e5b8014..586f70c 100644 --- a/src/ThirdParty/SDL2/SDL2Bindings.cs +++ b/src/ThirdParty/SDL2/SDL2Bindings.cs @@ -4319,7 +4319,7 @@ uint color /* surface refers to an SDL_Surface* */ [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] - public static extern void SDL_FreeSurface(IntPtr surface); + public static extern unsafe void SDL_FreeSurface(SDL_Surface* surface); /* surface refers to an SDL_Surface* */ [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] diff --git a/src/Womb/Backends/OpenGL/Api/Constants/OpenGL1_0.fs b/src/Womb/Backends/OpenGL/Api/Constants/OpenGL1_0.fs index 04220f0..de21bfa 100644 --- a/src/Womb/Backends/OpenGL/Api/Constants/OpenGL1_0.fs +++ b/src/Womb/Backends/OpenGL/Api/Constants/OpenGL1_0.fs @@ -348,4 +348,4 @@ let GL_TEXTURE_WRAP_S = 0x2802u [] let GL_TEXTURE_WRAP_T = 0x2803u [] -let GL_REPEAT = 0x2901u \ No newline at end of file +let GL_REPEAT = 0x2901u diff --git a/src/Womb/Backends/OpenGL/Api/OpenGL1_1.fs b/src/Womb/Backends/OpenGL/Api/OpenGL1_1.fs index 2b55fec..2132450 100644 --- a/src/Womb/Backends/OpenGL/Api/OpenGL1_1.fs +++ b/src/Womb/Backends/OpenGL/Api/OpenGL1_1.fs @@ -1,8 +1,11 @@ [] module Womb.Backends.OpenGL.Api.OpenGL1_1 +#nowarn "9" // Unverifiable IL due to fixed expression and NativePtr library usage + open Womb.Backends.OpenGL.Api.Common open Womb.Logging +open Microsoft.FSharp.NativeInterop type private DrawArrays = delegate of uint * int * int -> unit let mutable private _glDrawArrays = @@ -70,10 +73,18 @@ let mutable private _glDeleteTextures = DeleteTextures(fun _ _ -> warn (notLinked())) let glDeleteTextures n textures = _glDeleteTextures.Invoke(n, textures) -type private GenTextures = delegate of int * unativeint -> unit +type private GenTextures = delegate of int * nativeptr -> unit let mutable private _glGenTextures = GenTextures(fun _ _ -> warn (notLinked())) -let glGenTextures n textures = _glGenTextures.Invoke(n, textures) +let glGenTextures n = + let textures = Array.zeroCreate n + use ptr = fixed (&(textures.[0])) in + _glGenTextures.Invoke(n, ptr) + textures +let glGenTexture() = + let ptr = NativePtr.stackalloc 1 + _glGenTextures.Invoke(1, ptr) + NativePtr.get ptr 0 type private IsTexture = delegate of uint -> unit let mutable private _glIsTexture = diff --git a/src/Womb/Graphics/Primitives.fs b/src/Womb/Graphics/Primitives.fs index 4b0aa2b..07246ba 100644 --- a/src/Womb/Graphics/Primitives.fs +++ b/src/Womb/Graphics/Primitives.fs @@ -14,19 +14,50 @@ type ShadedObjectContext = VBO: uint; EBO: uint; Vertices: array; - Indices: array; } + Indices: array; + Texture: uint; } static member Default () = { VAO = 0u VBO = 0u EBO = 0u Vertices = Array.empty - Indices = Array.empty } + Indices = Array.empty + Texture = 0u } - static member From (vertices) (indices) : ShadedObjectContext = + static member From (vertices) (indices) (textureOpt: option) : ShadedObjectContext = let vao = glGenVertexArray() let vbo = glGenBuffer() let ebo = glGenBuffer() + let textureId = + match textureOpt with + | Some texture -> + let id = glGenTexture() + glBindTexture GL_TEXTURE_2D id + + // NOTE: Eventually we will add texturing options for the individual texture, but for now let's set some defaults + glTexParameteri GL_TEXTURE_2D GL_TEXTURE_WRAP_S (int GL_REPEAT) + glTexParameteri GL_TEXTURE_2D GL_TEXTURE_WRAP_T (int GL_REPEAT) + glTexParameteri GL_TEXTURE_2D GL_TEXTURE_MIN_FILTER (int GL_LINEAR_MIPMAP_LINEAR) + glTexParameteri GL_TEXTURE_2D GL_TEXTURE_MAG_FILTER (int GL_LINEAR) + + glTexImage2D + GL_TEXTURE_2D // specifies the dimensional behavior of a texture: 1D is a line, 2D is a normal flat texture, 3D is a volumetric texture, like when a model is cut open and you want to show the interior of the object: https://stackoverflow.com/a/7329678 + 0 // mipmap level to use (clamped to 0 for now) + (int GL_RGB) // format of output texture + texture.Width // width of resulting texture + texture.Height // height of resulting texture + 0 // always 0, for legacy reasons + GL_RGB // format of source image + GL_UNSIGNED_BYTE // datatype of source image + texture.Data // image data + glGenerateMipmap GL_TEXTURE_2D + + match texture.Surface with + | Some surface -> SDL2Bindings.SDL.SDL_FreeSurface(surface) + | None -> () + id + | None -> 0u glBindVertexArray vao @@ -55,11 +86,25 @@ type ShadedObjectContext = 0 glEnableVertexAttribArray 0u + match textureId with + | 0u -> + // TODO: This is not binding properly. Create a new buffer just for texture coordinates and bind it + glVertexAttribPointer + 1u + 2u + GL_FLOAT + false + 2 + 4 + glEnableVertexAttribArray 1u + | _ -> () + { VAO = vao VBO = vbo EBO = ebo Vertices = vertices - Indices = indices } + Indices = indices + Texture = textureId } static member UpdateIndices (indices) (context: ShadedObjectContext) : ShadedObjectContext = glBindBuffer @@ -85,6 +130,8 @@ type ShadedObjectContext = { context with Vertices = vertices } + // TODO: Allow for dynamic update of texture + static member Update (vertices) (indices) (context: ShadedObjectContext) : ShadedObjectContext = ShadedObjectContext.UpdateVertices vertices context |> ShadedObjectContext.UpdateIndices indices @@ -108,13 +155,14 @@ type ShadedObject = else None - static member CreateQuad vertexPaths fragmentPaths transform width height = + static member CreateQuad vertexPaths fragmentPaths transform width height texture = match ( Display.compileShader vertexPaths fragmentPaths ) with | Some shader -> + // TODO: Construct tex coords let vertices = [| // bottom left -width / 2.0f; -height / 2.0f; 0.0f; @@ -130,7 +178,7 @@ type ShadedObject = 1u; 2u; 3u; // second triangle vertex order as array indices |] Quad( - ShadedObjectContext.From vertices indices, + ShadedObjectContext.From vertices indices texture, shader, transform, width, @@ -160,6 +208,8 @@ type ShadedObject = height ) + // TODO: Allow for dynamic update of texture + static member Update (vertices) (indices) (primitive: ShadedObject) : ShadedObject = match primitive with | Quad(context, shader, transform, width, height) -> Quad( @@ -184,7 +234,7 @@ type ShadedObject = let mvp = modelMatrix * viewMatrix * projectionMatrix glUniformMatrix4fv mvpUniform 1 mvp - + List.map ( fun uniformData -> @@ -261,12 +311,24 @@ type ShadedObject = | 3 -> glUniform3ui location data[0] data[1] data[2] | 4 -> glUniform4ui location data[0] data[1] data[2] data[3] | len -> fail $"Unsupported UVectorUniform length {len} when trying to use shader" + | SamplerUniform(name, data) -> + let location = glGetUniformLocation shader.Id name + glUniform1i location data ) uniforms |> ignore static member Draw<'T> (config:Config<'T>) (viewMatrix:System.Numerics.Matrix4x4) (projectionMatrix:System.Numerics.Matrix4x4) (primitive:ShadedObject) (uniforms) = match primitive with | Quad(context, shader, transform, width, height) -> + let all_uniforms = + match context.Texture with + | 0u -> uniforms + | _ -> [ + SamplerUniform( + "in_texture", + 0 + ) + ] @ uniforms ShadedObject.UseMvpShader config shader @@ -286,8 +348,13 @@ type ShadedObject = config.DisplayConfig.Height |> single ) ); - ] @ uniforms ) - + ] @ all_uniforms ) + + match context.Texture with + | 0u -> () + | _ -> + glActiveTexture GL_TEXTURE0 + glBindTexture GL_TEXTURE_2D context.Texture glBindVertexArray context.VAO glBindBuffer GL_ELEMENT_ARRAY_BUFFER diff --git a/src/Womb/Graphics/Types.fs b/src/Womb/Graphics/Types.fs index 7486c67..8b3acff 100644 --- a/src/Womb/Graphics/Types.fs +++ b/src/Womb/Graphics/Types.fs @@ -1,7 +1,12 @@ module Womb.Graphics.Types +#nowarn "9" // unverifiable IL due to NativePtr library usage + +open Microsoft.FSharp.NativeInterop + open SDL2Bindings open Womb.Lib.Types +open Womb.Logging type DisplayConfig = { Width: uint; @@ -48,3 +53,31 @@ type Uniform = | UVector3Uniform of Name:string * Data:UVector3 | UVector4Uniform of Name:string * Data:UVector4 | UVectorUniform of Name:string * Data:array + | SamplerUniform of Name:string * Data:IVector1 + +type Texture2D = + { Surface: option>; + Width: int; + Height: int; + Data: voidptr; + Format: nativeint; } + + static member Default() = + { Surface = None + Width = 0 + Height = 0 + Data = 0n.ToPointer() + Format = 0n } + + static member FromPath path = + let surfacePtr = Image.IMG_Load(path) + if NativePtr.isNullPtr surfacePtr then + fail $"Failed to load surface from path - {path}" + Texture2D.Default() + else + let surface = NativePtr.get surfacePtr 0 + { Surface = Some surfacePtr + Width = surface.w + Height = surface.h + Data = surface.pixels.ToPointer() + Format = surface.format }