Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

3: Add texture support #47

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
12 changes: 9 additions & 3 deletions examples/Playground/Program.fs
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,19 @@ type GameState =
Triangles = Primitives.ShadedObject.Default }

let private initHandler (config:Config<GameState>) =
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 }
Expand Down
Original file line number Diff line number Diff line change
@@ -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()
{
Expand Down
19 changes: 19 additions & 0 deletions examples/Playground/Resources/Shaders/Texture/fragment.glsl
Original file line number Diff line number Diff line change
@@ -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
);
}
12 changes: 12 additions & 0 deletions examples/Playground/Resources/Shaders/Texture/vertex.glsl
Original file line number Diff line number Diff line change
@@ -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;
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 6 additions & 5 deletions issues/in-progress.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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.
6 changes: 3 additions & 3 deletions src/ThirdParty/SDL2/Image.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion src/ThirdParty/SDL2/SDL2Bindings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
2 changes: 1 addition & 1 deletion src/Womb/Backends/OpenGL/Api/Constants/OpenGL1_0.fs
Original file line number Diff line number Diff line change
Expand Up @@ -348,4 +348,4 @@ let GL_TEXTURE_WRAP_S = 0x2802u
[<Literal>]
let GL_TEXTURE_WRAP_T = 0x2803u
[<Literal>]
let GL_REPEAT = 0x2901u
let GL_REPEAT = 0x2901u
15 changes: 13 additions & 2 deletions src/Womb/Backends/OpenGL/Api/OpenGL1_1.fs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
[<AutoOpen>]
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 =
Expand Down Expand Up @@ -70,10 +73,18 @@ let mutable private _glDeleteTextures =
DeleteTextures(fun _ _ -> warn (notLinked<DeleteTextures>()))
let glDeleteTextures n textures = _glDeleteTextures.Invoke(n, textures)

type private GenTextures = delegate of int * unativeint -> unit
type private GenTextures = delegate of int * nativeptr<uint> -> unit
let mutable private _glGenTextures =
GenTextures(fun _ _ -> warn (notLinked<GenTextures>()))
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<uint> 1
_glGenTextures.Invoke(1, ptr)
NativePtr.get ptr 0

type private IsTexture = delegate of uint -> unit
let mutable private _glIsTexture =
Expand Down
85 changes: 76 additions & 9 deletions src/Womb/Graphics/Primitives.fs
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,50 @@ type ShadedObjectContext =
VBO: uint;
EBO: uint;
Vertices: array<single>;
Indices: array<uint>; }
Indices: array<uint>;
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<Texture2D>) : 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

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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;
Expand All @@ -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,
Expand Down Expand Up @@ -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(
Expand All @@ -184,7 +234,7 @@ type ShadedObject =

let mvp = modelMatrix * viewMatrix * projectionMatrix
glUniformMatrix4fv mvpUniform 1 mvp

List.map
(
fun uniformData ->
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
33 changes: 33 additions & 0 deletions src/Womb/Graphics/Types.fs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -48,3 +53,31 @@ type Uniform =
| UVector3Uniform of Name:string * Data:UVector3
| UVector4Uniform of Name:string * Data:UVector4
| UVectorUniform of Name:string * Data:array<uint>
| SamplerUniform of Name:string * Data:IVector1

type Texture2D =
{ Surface: option<nativeptr<SDL.SDL_Surface>>;
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 }
Loading