From e83f8a5fd9d31f25710f9a82bf490987e77f9e16 Mon Sep 17 00:00:00 2001 From: "kts of kettek (POWERQWACK)" Date: Mon, 5 Oct 2020 23:45:32 -0700 Subject: [PATCH 1/3] Implement min/mag texture filtering These changes allow users to call SetTextureMinFilter and SetTextureMagFilter on the MasterWindow to change how textures are filtered for rendering. This allows pixel-perfect rendering via giu.TextureFilterNearest. --- MasterWindow.go | 20 +++++++++++++ TextureFilters.go | 11 ++++++++ imgui/RendererInterface.go | 4 +++ imgui/RendererOpenGL3.go | 58 +++++++++++++++++++++++++++++++++++--- 4 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 TextureFilters.go diff --git a/MasterWindow.go b/MasterWindow.go index c29c8e3b..37e63417 100644 --- a/MasterWindow.go +++ b/MasterWindow.go @@ -210,6 +210,26 @@ func (w *MasterWindow) SetDropCallback(cb func([]string)) { w.platform.SetDropCallback(cb) } +// SetTextureMinFilter sets the minifying function for texture rendering. +func (w *MasterWindow) SetTextureMinFilter(min uint) error { + if w.renderer != nil { + if err := w.renderer.SetTextureMinFilter(min); err != nil { + return err + } + } + return nil +} + +// SetTextureMagFilter sets the magnifying function for texture rendering. +func (w *MasterWindow) SetTextureMagFilter(mag uint) error { + if w.renderer != nil { + if err := w.renderer.SetTextureMagFilter(mag); err != nil { + return err + } + } + return nil +} + // Call the main loop. // loopFunc will be used to construct the ui. func (w *MasterWindow) Main(loopFunc func()) { diff --git a/TextureFilters.go b/TextureFilters.go new file mode 100644 index 00000000..debcfda1 --- /dev/null +++ b/TextureFilters.go @@ -0,0 +1,11 @@ +package giu + +// Texture filtering types. +const ( + TextureFilterNearest = iota + TextureFilterLinear + TextureFilterNearestMipmapNearest + TextureFilterLinearMipmapNearest + TextureFilterNearestMipmapLinear + TextureFilterLinearMipmapLinear +) diff --git a/imgui/RendererInterface.go b/imgui/RendererInterface.go index 909ee2ac..f44cd075 100644 --- a/imgui/RendererInterface.go +++ b/imgui/RendererInterface.go @@ -8,6 +8,10 @@ type Renderer interface { PreRender(clearColor [4]float32) // Render draws the provided imgui draw data. Render(displaySize [2]float32, framebufferSize [2]float32, drawData DrawData) + // Sets the texture minifying filter. + SetTextureMinFilter(min uint) error + // Sets the texture magnifying filter. + SetTextureMagFilter(mag uint) error // Load image and return the TextureID LoadImage(image *image.RGBA) (TextureID, error) // Release image diff --git a/imgui/RendererOpenGL3.go b/imgui/RendererOpenGL3.go index 6d679ba4..20f82e3b 100644 --- a/imgui/RendererOpenGL3.go +++ b/imgui/RendererOpenGL3.go @@ -28,9 +28,21 @@ type OpenGL3 struct { vboHandle uint32 elementsHandle uint32 - contentScale float32 + contentScale float32 + textureMinFilter int32 + textureMagFilter int32 } +// Texture filtering types. +const ( + textureFilterNearest = iota + textureFilterLinear + textureFilterNearestMipmapNearest + textureFilterLinearMipmapNearest + textureFilterNearestMipmapLinear + textureFilterLinearMipmapLinear +) + // NewOpenGL3 attempts to initialize a renderer. // An OpenGL context has to be established before calling this function. func NewOpenGL3(io IO, contentScale float32) (*OpenGL3, error) { @@ -40,9 +52,11 @@ func NewOpenGL3(io IO, contentScale float32) (*OpenGL3, error) { } renderer := &OpenGL3{ - imguiIO: io, - glslVersion: "#version 150", - contentScale: contentScale, + imguiIO: io, + glslVersion: "#version 150", + contentScale: contentScale, + textureMinFilter: gl.LINEAR, + textureMagFilter: gl.LINEAR, } renderer.createDeviceObjects() return renderer, nil @@ -176,6 +190,8 @@ func (renderer *OpenGL3) Render(displaySize [2]float32, framebufferSize [2]float gl.Uniform1i(renderer.attribLocationImageType, int32(imageType)) gl.BindTexture(gl.TEXTURE_2D, uint32(cmd.TextureID())) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, renderer.textureMinFilter) // minification filter + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, renderer.textureMagFilter) // magnification filter clipRect := cmd.ClipRect() gl.Scissor(int32(clipRect.X), int32(fbHeight)-int32(clipRect.W), int32(clipRect.Z-clipRect.X), int32(clipRect.W-clipRect.Y)) gl.DrawElements(gl.TRIANGLES, int32(cmd.ElementCount()), uint32(drawType), unsafe.Pointer(indexBufferOffset)) @@ -384,6 +400,40 @@ func (renderer *OpenGL3) invalidateDeviceObjects() { } } +// SetTextureMinFilter sets the minifying function for texture filtering. +func (renderer *OpenGL3) SetTextureMinFilter(min uint) error { + switch min { + case textureFilterNearest: + renderer.textureMinFilter = gl.NEAREST + case textureFilterLinear: + renderer.textureMinFilter = gl.LINEAR + case textureFilterNearestMipmapNearest: + renderer.textureMinFilter = gl.NEAREST_MIPMAP_NEAREST + case textureFilterLinearMipmapNearest: + renderer.textureMinFilter = gl.LINEAR_MIPMAP_NEAREST + case textureFilterNearestMipmapLinear: + renderer.textureMinFilter = gl.NEAREST_MIPMAP_LINEAR + case textureFilterLinearMipmapLinear: + renderer.textureMinFilter = gl.LINEAR_MIPMAP_LINEAR + default: + return fmt.Errorf("invalid minifying filter") + } + return nil +} + +// SetTextureMagFilter sets the magnifying function for texture filtering. +func (renderer *OpenGL3) SetTextureMagFilter(mag uint) error { + switch mag { + case textureFilterNearest: + renderer.textureMagFilter = gl.NEAREST + case textureFilterLinear: + renderer.textureMagFilter = gl.LINEAR + default: + return fmt.Errorf("invalid magnifying filter") + } + return nil +} + // Load image and return the TextureID func (renderer *OpenGL3) LoadImage(image *image.RGBA) (TextureID, error) { texture, err := renderer.createImageTexture(image) From 06a207d2cb1e1bba077c1ee57253660fa83b4a73 Mon Sep 17 00:00:00 2001 From: "kts of kettek (POWERQWACK)" Date: Tue, 6 Oct 2020 02:28:05 -0700 Subject: [PATCH 2/3] Remove min and mag filter setters --- MasterWindow.go | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/MasterWindow.go b/MasterWindow.go index 37e63417..c29c8e3b 100644 --- a/MasterWindow.go +++ b/MasterWindow.go @@ -210,26 +210,6 @@ func (w *MasterWindow) SetDropCallback(cb func([]string)) { w.platform.SetDropCallback(cb) } -// SetTextureMinFilter sets the minifying function for texture rendering. -func (w *MasterWindow) SetTextureMinFilter(min uint) error { - if w.renderer != nil { - if err := w.renderer.SetTextureMinFilter(min); err != nil { - return err - } - } - return nil -} - -// SetTextureMagFilter sets the magnifying function for texture rendering. -func (w *MasterWindow) SetTextureMagFilter(mag uint) error { - if w.renderer != nil { - if err := w.renderer.SetTextureMagFilter(mag); err != nil { - return err - } - } - return nil -} - // Call the main loop. // loopFunc will be used to construct the ui. func (w *MasterWindow) Main(loopFunc func()) { From 23b3e247b9c3a76c431f1abe92f0b005a2779432 Mon Sep 17 00:00:00 2001 From: "kts of kettek (POWERQWACK)" Date: Tue, 6 Oct 2020 04:53:18 -0700 Subject: [PATCH 3/3] Add min/mag texture filtering example --- examples/texturefiltering/gopher-sprite.png | Bin 0 -> 501 bytes examples/texturefiltering/gopher.png | Bin 0 -> 19040 bytes examples/texturefiltering/texturefiltering.go | 93 ++++++++++++++++++ 3 files changed, 93 insertions(+) create mode 100644 examples/texturefiltering/gopher-sprite.png create mode 100644 examples/texturefiltering/gopher.png create mode 100644 examples/texturefiltering/texturefiltering.go diff --git a/examples/texturefiltering/gopher-sprite.png b/examples/texturefiltering/gopher-sprite.png new file mode 100644 index 0000000000000000000000000000000000000000..fb24a8ab559ca0d074fb1a8038f0097835b6722f GIT binary patch literal 501 zcmVPx$uSrBfR5*=|l)s7-Q5431XEs>|8x=&?wHX&#oM>U|1DFRGL}YCQU%)4@u(lAa zeF2NuM=Kj!VNC{CaU&>Jg2l2s(aHTRCil)v63iaBaOQsJ`_3QE9RrAyRK+B>I7!7C z^Kr!_H;a>0x1T)c{nN)}!|^&UgMJSmZ$5K295d+m09J}Lu|dB_G081}agwTVPK9%d zlT>>L3TF{(sd{jC_qt9>X*w$vC#fEMu831fg>$-ec}qv7f-YZhlRFLWclUo;7i0bbky8ryu%92K00000NkvXXu0mjfj(X!O literal 0 HcmV?d00001 diff --git a/examples/texturefiltering/gopher.png b/examples/texturefiltering/gopher.png new file mode 100644 index 0000000000000000000000000000000000000000..0587e70926125e5f6a0928ee5006726852416b00 GIT binary patch literal 19040 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