diff --git a/docs/topics/texture_formats.rst b/docs/topics/texture_formats.rst index 467660ad..b6127c34 100644 --- a/docs/topics/texture_formats.rst +++ b/docs/topics/texture_formats.rst @@ -198,6 +198,87 @@ In shaders the sampler type should be ``isampler2D``, ``isampler2DArray`` | i4 | 4 | GL_RGBA_INTEGER | GL_RGBA32I | +----------+---------------+-----------------+-------------------+ +Normalized Integer Textures +--------------------------- + +Normalized integers are integer texture, but texel reads in a shader +returns normalized values (``[0.0, 1.0]``). For example an unsigned 16 +bit fragment with the value ``2**16-1`` will be read as ``1.0``. + +Normalized integer textures should use the `sampler2D` sampler +type. Also note that there's no standard for normalized 32 bit +integer textures because a float32 doesn't have enough precision +to express a 32 bit integer as a number between 0.0 and 1.0. + +Unsigned +~~~~~~~~ + +``nu1`` textures is really the same as an ``f1``. Each component +is a ``GL_UNSIGNED_BYTE``, but are read by the shader in normalized +form ``[0.0, 1.0]``. + ++----------+---------------+-----------------+-------------------+ +| **dtype**| *Components* | *Base Format* | *Internal Format* | ++==========+===============+=================+===================+ +| nu1 | 1 | GL_RED | GL_R8 | ++----------+---------------+-----------------+-------------------+ +| nu1 | 2 | GL_RG | GL_RG8 | ++----------+---------------+-----------------+-------------------+ +| nu1 | 3 | GL_RGB | GL_RGB8 | ++----------+---------------+-----------------+-------------------+ +| nu1 | 4 | GL_RGBA | GL_RGBA8 | ++----------+---------------+-----------------+-------------------+ + +``nu2`` textures store 16 bit unsigned integers (``GL_UNSIGNED_SHORT``). +The value range ``[0, 2**16-1]`` will be normalized into ``[0.0, 1.0]``. + ++----------+---------------+-----------------+-------------------+ +| **dtype**| *Components* | *Base Format* | *Internal Format* | ++==========+===============+=================+===================+ +| nu2 | 1 | GL_RED | GL_R16 | ++----------+---------------+-----------------+-------------------+ +| nu2 | 2 | GL_RG | GL_RG16 | ++----------+---------------+-----------------+-------------------+ +| nu2 | 3 | GL_RGB | GL_RGB16 | ++----------+---------------+-----------------+-------------------+ +| nu2 | 4 | GL_RGBA | GL_RGBA16 | ++----------+---------------+-----------------+-------------------+ + +Signed +~~~~~~ + +``ni1`` textures store 8 bit signed integers (``GL_BYTE``). +The value range ``[0, 127]`` will be normalized into ``[0.0, 1.0]``. +Negative values will be clamped. + ++----------+---------------+-----------------+-------------------+ +| **dtype**| *Components* | *Base Format* | *Internal Format* | ++==========+===============+=================+===================+ +| ni1 | 1 | GL_RED | GL_R8 | ++----------+---------------+-----------------+-------------------+ +| ni1 | 2 | GL_RG | GL_RG8 | ++----------+---------------+-----------------+-------------------+ +| ni1 | 3 | GL_RGB | GL_RGB8 | ++----------+---------------+-----------------+-------------------+ +| ni1 | 4 | GL_RGBA | GL_RGBA8 | ++----------+---------------+-----------------+-------------------+ + +``ni2`` textures store 16 bit signed integers (``GL_SHORT``). +The value range ``[0, 2**15-1]`` will be normalized into ``[0.0, 1.0]``. +Negative values will be clamped. + ++----------+---------------+-----------------+-------------------+ +| **dtype**| *Components* | *Base Format* | *Internal Format* | ++==========+===============+=================+===================+ +| ni2 | 1 | GL_RED | GL_R16 | ++----------+---------------+-----------------+-------------------+ +| ni2 | 2 | GL_RG | GL_RG16 | ++----------+---------------+-----------------+-------------------+ +| ni2 | 3 | GL_RGB | GL_RGB16 | ++----------+---------------+-----------------+-------------------+ +| ni2 | 4 | GL_RGBA | GL_RGBA16 | ++----------+---------------+-----------------+-------------------+ + Overriding internalformat ------------------------- diff --git a/moderngl/src/DataType.cpp b/moderngl/src/DataType.cpp index 7949fd09..faf5d410 100644 --- a/moderngl/src/DataType.cpp +++ b/moderngl/src/DataType.cpp @@ -13,6 +13,9 @@ static int i1_internal_format[5] = {0, GL_R8I, GL_RG8I, GL_RGB8I, GL_RGBA8I}; static int i2_internal_format[5] = {0, GL_R16I, GL_RG16I, GL_RGB16I, GL_RGBA16I}; static int i4_internal_format[5] = {0, GL_R32I, GL_RG32I, GL_RGB32I, GL_RGBA32I}; +static int n1_internal_format[5] = {0, GL_R8, GL_RG8, GL_RGB8, GL_RGBA8}; +static int n2_internal_format[5] = {0, GL_R16, GL_RG16, GL_RGB16, GL_RGBA16}; + static MGLDataType f1 = {float_base_format, f1_internal_format, GL_UNSIGNED_BYTE, 1, true}; static MGLDataType f2 = {float_base_format, f2_internal_format, GL_HALF_FLOAT, 2, true}; static MGLDataType f4 = {float_base_format, f4_internal_format, GL_FLOAT, 4, true}; @@ -23,40 +26,69 @@ static MGLDataType i1 = {int_base_format, i1_internal_format, GL_BYTE, 1, false} static MGLDataType i2 = {int_base_format, i2_internal_format, GL_SHORT, 2, false}; static MGLDataType i4 = {int_base_format, i4_internal_format, GL_INT, 4, false}; -MGLDataType * from_dtype(const char * dtype) { - if (!dtype[0] || (dtype[1] && dtype[2])) { - return 0; - } +static MGLDataType nu1 = {float_base_format, n1_internal_format, GL_UNSIGNED_BYTE, 1, false}; +static MGLDataType nu2 = {float_base_format, n2_internal_format, GL_UNSIGNED_SHORT, 2, false}; +static MGLDataType ni1 = {float_base_format, n1_internal_format, GL_BYTE, 1, false}; +static MGLDataType ni2 = {float_base_format, n2_internal_format, GL_SHORT, 2, false}; + +MGLDataType * from_dtype(const char * dtype, Py_ssize_t size) { + if (size < 2 || size > 3) return 0; + + // if (!dtype[0] || (dtype[1] && dtype[2])) { + // return 0; + // } + + if (size == 2) { + switch (dtype[0] * 256 + dtype[1]) { + case ('f' * 256 + '1'): + return &f1; - switch (dtype[0] * 256 + dtype[1]) { - case ('f' * 256 + '1'): - return &f1; + case ('f' * 256 + '2'): + return &f2; - case ('f' * 256 + '2'): - return &f2; + case ('f' * 256 + '4'): + return &f4; - case ('f' * 256 + '4'): - return &f4; + case ('u' * 256 + '1'): + return &u1; - case ('u' * 256 + '1'): - return &u1; + case ('u' * 256 + '2'): + return &u2; - case ('u' * 256 + '2'): - return &u2; + case ('u' * 256 + '4'): + return &u4; - case ('u' * 256 + '4'): - return &u4; + case ('i' * 256 + '1'): + return &i1; + + case ('i' * 256 + '2'): + return &i2; + + case ('i' * 256 + '4'): + return &i4; + + default: + return 0; + } + } + else if (size == 3) + { + switch (dtype[0] * 65536 + dtype[1] * 256 + dtype[2]) + { + case ('n' * 65536 + 'i' * 256 + '1'): + return &ni1; - case ('i' * 256 + '1'): - return &i1; + case ('n' * 65536 + 'i' * 256 + '2'): + return &ni2; - case ('i' * 256 + '2'): - return &i2; + case ('n' * 65536 + 'u' * 256 + '1'): + return &nu1; - case ('i' * 256 + '4'): - return &i4; + case ('n' * 65536 + 'u' * 256 + '2'): + return &nu2; - default: - return 0; + default: + return 0; + } } } diff --git a/moderngl/src/Framebuffer.cpp b/moderngl/src/Framebuffer.cpp index a04c862f..00c911a5 100644 --- a/moderngl/src/Framebuffer.cpp +++ b/moderngl/src/Framebuffer.cpp @@ -530,12 +530,7 @@ PyObject * MGLFramebuffer_read(MGLFramebuffer * self, PyObject * args) { return 0; } - if (dtype_size != 2) { - MGLError_Set("invalid dtype"); - return 0; - } - - MGLDataType * data_type = from_dtype(dtype); + MGLDataType * data_type = from_dtype(dtype, dtype_size); if (!data_type) { MGLError_Set("invalid dtype"); @@ -651,12 +646,7 @@ PyObject * MGLFramebuffer_read_into(MGLFramebuffer * self, PyObject * args) { return 0; } - if (dtype_size != 2) { - MGLError_Set("invalid dtype"); - return 0; - } - - MGLDataType * data_type = from_dtype(dtype); + MGLDataType * data_type = from_dtype(dtype, dtype_size); if (!data_type) { MGLError_Set("invalid dtype"); diff --git a/moderngl/src/Renderbuffer.cpp b/moderngl/src/Renderbuffer.cpp index 6d6f03b0..2a7868b5 100644 --- a/moderngl/src/Renderbuffer.cpp +++ b/moderngl/src/Renderbuffer.cpp @@ -36,12 +36,7 @@ PyObject * MGLContext_renderbuffer(MGLContext * self, PyObject * args) { return 0; } - if (dtype_size != 2) { - MGLError_Set("invalid dtype"); - return 0; - } - - MGLDataType * data_type = from_dtype(dtype); + MGLDataType * data_type = from_dtype(dtype, dtype_size); if (!data_type) { MGLError_Set("invalid dtype"); @@ -137,7 +132,7 @@ PyObject * MGLContext_depth_renderbuffer(MGLContext * self, PyObject * args) { renderbuffer->height = height; renderbuffer->components = 1; renderbuffer->samples = samples; - renderbuffer->data_type = from_dtype("f4"); + renderbuffer->data_type = from_dtype("f4", 2); renderbuffer->depth = true; Py_INCREF(self); diff --git a/moderngl/src/Texture.cpp b/moderngl/src/Texture.cpp index 0b729abc..9b7cf1f5 100644 --- a/moderngl/src/Texture.cpp +++ b/moderngl/src/Texture.cpp @@ -55,12 +55,7 @@ PyObject * MGLContext_texture(MGLContext * self, PyObject * args) { return 0; } - if (dtype_size != 2) { - MGLError_Set("invalid dtype"); - return 0; - } - - MGLDataType * data_type = from_dtype(dtype); + MGLDataType * data_type = from_dtype(dtype, dtype_size); if (!data_type) { MGLError_Set("invalid dtype"); @@ -259,7 +254,7 @@ PyObject * MGLContext_depth_texture(MGLContext * self, PyObject * args) { texture->height = height; texture->components = 1; texture->samples = samples; - texture->data_type = from_dtype("f4"); + texture->data_type = from_dtype("f4", 2); texture->compare_func = GL_LEQUAL; texture->depth = true; diff --git a/moderngl/src/Texture3D.cpp b/moderngl/src/Texture3D.cpp index 7b10e85b..da651719 100644 --- a/moderngl/src/Texture3D.cpp +++ b/moderngl/src/Texture3D.cpp @@ -43,12 +43,7 @@ PyObject * MGLContext_texture3d(MGLContext * self, PyObject * args) { return 0; } - if (dtype_size != 2) { - MGLError_Set("invalid dtype"); - return 0; - } - - MGLDataType * data_type = from_dtype(dtype); + MGLDataType * data_type = from_dtype(dtype, dtype_size); if (!data_type) { MGLError_Set("invalid dtype"); diff --git a/moderngl/src/TextureArray.cpp b/moderngl/src/TextureArray.cpp index 08a025f2..a2a27db9 100644 --- a/moderngl/src/TextureArray.cpp +++ b/moderngl/src/TextureArray.cpp @@ -43,12 +43,7 @@ PyObject * MGLContext_texture_array(MGLContext * self, PyObject * args) { return 0; } - if (dtype_size != 2) { - MGLError_Set("invalid dtype"); - return 0; - } - - MGLDataType * data_type = from_dtype(dtype); + MGLDataType * data_type = from_dtype(dtype, dtype_size); if (!data_type) { MGLError_Set("invalid dtype"); diff --git a/moderngl/src/TextureCube.cpp b/moderngl/src/TextureCube.cpp index 308ed56e..85c377eb 100644 --- a/moderngl/src/TextureCube.cpp +++ b/moderngl/src/TextureCube.cpp @@ -41,12 +41,7 @@ PyObject * MGLContext_texture_cube(MGLContext * self, PyObject * args) { return 0; } - if (dtype_size != 2) { - MGLError_Set("invalid dtype"); - return 0; - } - - MGLDataType * data_type = from_dtype(dtype); + MGLDataType * data_type = from_dtype(dtype, dtype_size); if (!data_type) { MGLError_Set("invalid dtype"); diff --git a/moderngl/src/Types.hpp b/moderngl/src/Types.hpp index d1006fad..1201f293 100644 --- a/moderngl/src/Types.hpp +++ b/moderngl/src/Types.hpp @@ -417,7 +417,7 @@ struct MGLSampler { float max_lod; }; -MGLDataType * from_dtype(const char * dtype); +MGLDataType * from_dtype(const char * dtype, Py_ssize_t size); void MGLAttribute_Invalidate(MGLAttribute * attribute); void MGLBuffer_Invalidate(MGLBuffer * buffer); diff --git a/tests/test_texture.py b/tests/test_texture.py index aa30eb83..ce7e2cdf 100644 --- a/tests/test_texture.py +++ b/tests/test_texture.py @@ -1,5 +1,6 @@ import struct import unittest +from array import array import platform import moderngl @@ -161,6 +162,52 @@ def test_override_internalformat(self): data = texture.read() self.assertEqual(self.ctx.error, "GL_NO_ERROR") + def test_normalized_textures(self): + """8 and 16 bit normalized integer textures""" + ni1 = self.ctx.texture((4, 4), 4, dtype="ni1") + ni2 = self.ctx.texture((4, 4), 4, dtype="ni2") + nu1 = self.ctx.texture((4, 4), 4, dtype="nu1") + nu2 = self.ctx.texture((4, 4), 4, dtype="nu2") + + nu1_data = array('B', [255] * 16 * 4).tobytes() + nu2_data = array('H', [65535] * 16 * 4).tobytes() + ni1_data = array('b', [127] * 16 * 4).tobytes() + ni2_data = array('h', [32767] * 16 * 4).tobytes() + + nu1.write(nu1_data) + nu2.write(nu2_data) + ni1.write(ni1_data) + ni2.write(ni2_data) + + self.assertEqual(nu1.read(), nu1_data) + self.assertEqual(nu2.read(), nu2_data) + self.assertEqual(ni1.read(), ni1_data) + self.assertEqual(ni2.read(), ni2_data) + + fbo = self.ctx.simple_framebuffer((4, 4)) + fbo.use() + + # Render these textures to an RGBA8 framebuffer and ensure the result is a pure white color (FF) + fbo.clear() + nu1.use() + self.vao.render() + self.assertEqual(fbo.read(viewport=(0, 0, 1, 1), components=4, dtype="f1"), b'\xff\xff\xff\xff') + + fbo.clear() + nu2.use() + self.vao.render() + self.assertEqual(fbo.read(viewport=(0, 0, 1, 1), components=4, dtype="f1"), b'\xff\xff\xff\xff') + + fbo.clear() + ni1.use() + self.vao.render() + self.assertEqual(fbo.read(viewport=(0, 0, 1, 1), components=4, dtype="f1"), b'\xff\xff\xff\xff') + + fbo.clear() + ni2.use() + self.vao.render() + self.assertEqual(fbo.read(viewport=(0, 0, 1, 1), components=4, dtype="f1"), b'\xff\xff\xff\xff') + if __name__ == '__main__': unittest.main()