diff options
author | Clément Foucault <foucault.clem@gmail.com> | 2022-01-12 15:03:07 +0300 |
---|---|---|
committer | Clément Foucault <foucault.clem@gmail.com> | 2022-01-12 15:05:18 +0300 |
commit | ec5560db738bb0329402f1bb3c86ca6a679fdb0a (patch) | |
tree | 799025f2bd3546ac462a17f478a8f271cc46b41b /source/blender/draw/intern | |
parent | 08820690953db8e28586fb2446a41d5160c70750 (diff) |
DRW: Add DRW_gpu_wrapper.hh
This adds wrapper classes that make it easier to use GPU objects in C++.
####Motivations:####
- Easier handling of GPU objects.
- EEVEE rewrite already makes use of similar wrappers.
- There is the ongoing effort to use more C++ in the codebase
and lans to port more engines to it.
- The shader code refactor will make use of many UBOs with shared
struct declaration. This helps managing them.
- Safer handling of `TextureFromPool` which can't be bound as normal
texture (only texture ref) and can be better tracked in the future.
####Considerations:####
- I chose the `blender::draw` namespace because `blender::gpu` already has private classes (i.e: `gpu::Texture`).
- Theses are wrappers that manage a GPU object internally. They might be confused with actual `Texture`. However, the name `TextureWrapper` is a bit too much verbose in my opinion. I'm open to suggestion about better name.
Reviewed By: jbakker
Differential Revision: http://developer.blender.org/D13805
Diffstat (limited to 'source/blender/draw/intern')
-rw-r--r-- | source/blender/draw/intern/DRW_gpu_wrapper.hh | 812 |
1 files changed, 812 insertions, 0 deletions
diff --git a/source/blender/draw/intern/DRW_gpu_wrapper.hh b/source/blender/draw/intern/DRW_gpu_wrapper.hh new file mode 100644 index 00000000000..01c57406dac --- /dev/null +++ b/source/blender/draw/intern/DRW_gpu_wrapper.hh @@ -0,0 +1,812 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Copyright 2022, Blender Foundation. + */ + +#pragma once + +/** \file + * \ingroup draw + * + * Wrapper classes that make it easier to use GPU objects in C++. + * + * All Buffers need to be sent to GPU memory before being used. This is done by using the + * `push_update()`. + * + * A Storage[Array]Buffer can hold much more data than a Uniform[Array]Buffer + * which can only holds 16KB of data. + * + * All types are not copyable and Buffers are not Movable. + * + * drw::UniformArrayBuffer<T, len> + * Uniform buffer object containing an array of T with len elements. + * Data can be accessed using the [] operator. + * + * drw::UniformBuffer<T> + * A uniform buffer object class inheriting from T. + * Data can be accessed just like a normal T object. + * + * drw::StorageArrayBuffer<T, len> + * Storage buffer object containing an array of T with len elements. + * The item count can be changed after creation using `resize()`. + * However, this requires the invalidation of the whole buffer and + * discarding all data inside it. + * Data can be accessed using the [] operator. + * + * drw::StorageBuffer<T> + * A storage buffer object class inheriting from T. + * Data can be accessed just like a normal T object. + * + * drw::Texture + * A simple wrapper to GPUTexture. A drw::Texture can be created without allocation. + * The `ensure_[1d|2d|3d|cube][_array]()` method is here to make sure the underlying texture + * will meet the requirements and create (or recreate) the GPUTexture if needed. + * + * drw::TextureFromPool + * A GPUTexture from the viewport texture pool. This texture can be shared with other engines + * and its content is undefined when aquiring it. + * A drw::TextureFromPool is acquired for rendering using `acquire()` and released once the + * rendering is done using `release()`. The same texture can be acquired & released multiple + * time in one draw loop. + * The `sync()` method *MUST* be called once during the cache populate (aka: Sync) phase. + * + * drw::Framebuffer + * Simple wrapper to GPUFramebuffer that can be moved. + * + */ + +#include "MEM_guardedalloc.h" + +#include "draw_texture_pool.h" + +#include "BLI_float4.hh" +#include "BLI_int2.hh" +#include "BLI_int3.hh" +#include "BLI_int4.hh" +#include "BLI_span.hh" +#include "BLI_utildefines.h" +#include "BLI_utility_mixins.hh" + +#include "GPU_framebuffer.h" +#include "GPU_texture.h" +#include "GPU_uniform_buffer.h" +#include "GPU_vertex_buffer.h" + +namespace blender::draw { + +/* -------------------------------------------------------------------- */ +/** \name Implementation Details + * \{ */ + +namespace detail { + +template< + /** Type of the values stored in this uniform buffer. */ + typename T, + /** The number of values that can be stored in this uniform buffer. */ + int64_t len, + /** True if the buffer only resides on GPU memory and cannot be accessed. */ + bool device_only> +class DataBuffer { + protected: + T *data_ = nullptr; + int64_t len_ = len; + + BLI_STATIC_ASSERT((sizeof(T) % 16) == 0, "Type need to be aligned to size of float4."); + + public: + /** + * Get the value at the given index. This invokes undefined behavior when the + * index is out of bounds. + */ + const T &operator[](int64_t index) const + { + BLI_STATIC_ASSERT(!device_only, ""); + BLI_assert(index >= 0); + BLI_assert(index < len); + return data_[index]; + } + + T &operator[](int64_t index) + { + BLI_STATIC_ASSERT(!device_only, ""); + BLI_assert(index >= 0); + BLI_assert(index < len); + return data_[index]; + } + + /** + * Get a pointer to the beginning of the array. + */ + const T *data() const + { + BLI_STATIC_ASSERT(!device_only, ""); + return data_; + } + T *data() + { + BLI_STATIC_ASSERT(!device_only, ""); + return data_; + } + + /** + * Iterator + */ + const T *begin() const + { + BLI_STATIC_ASSERT(!device_only, ""); + return data_; + } + const T *end() const + { + BLI_STATIC_ASSERT(!device_only, ""); + return data_ + len; + } + + T *begin() + { + BLI_STATIC_ASSERT(!device_only, ""); + return data_; + } + T *end() + { + BLI_STATIC_ASSERT(!device_only, ""); + return data_ + len; + } + + operator Span<T>() const + { + BLI_STATIC_ASSERT(!device_only, ""); + return Span<T>(data_, len); + } +}; + +template<typename T, int64_t len, bool device_only> +class UniformCommon : public DataBuffer<T, len, false>, NonMovable, NonCopyable { + protected: + GPUUniformBuf *ubo_; + +#ifdef DEBUG + const char *name_ = typeid(T).name(); +#else + constexpr static const char *name_ = "UniformBuffer"; +#endif + + public: + UniformCommon() + { + ubo_ = GPU_uniformbuf_create_ex(sizeof(T) * len, nullptr, name_); + } + + ~UniformCommon() + { + GPU_uniformbuf_free(ubo_); + } + + void push_update(void) + { + GPU_uniformbuf_update(ubo_, this->data_); + } + + /* To be able to use it with DRW_shgroup_*_ref(). */ + operator GPUUniformBuf *() const + { + return ubo_; + } + + /* To be able to use it with DRW_shgroup_*_ref(). */ + GPUUniformBuf **operator&() + { + return &ubo_; + } +}; + +template<typename T, int64_t len, bool device_only> +class StorageCommon : public DataBuffer<T, len, false>, NonMovable, NonCopyable { + protected: + /* Use vertex buffer for now. Until there is a complete GPUStorageBuf implementation. */ + GPUVertBuf *ssbo_; + +#ifdef DEBUG + const char *name_ = typeid(T).name(); +#else + constexpr static const char *name_ = "StorageBuffer"; +#endif + + public: + StorageCommon() + { + init(len); + } + + ~StorageCommon() + { + GPU_vertbuf_discard(ssbo_); + } + + void resize(int64_t new_size) + { + BLI_assert(new_size > 0); + if (new_size != this->len_) { + GPU_vertbuf_discard(ssbo_); + this->init(new_size); + } + } + + operator GPUVertBuf *() const + { + return ssbo_; + } + /* To be able to use it with DRW_shgroup_*_ref(). */ + GPUVertBuf **operator&() + { + return &ssbo_; + } + + private: + void init(int64_t new_size) + { + this->len_ = new_size; + + GPUVertFormat format = {0}; + GPU_vertformat_attr_add(&format, "dummy", GPU_COMP_F32, 1, GPU_FETCH_FLOAT); + + GPUUsageType usage = device_only ? GPU_USAGE_DEVICE_ONLY : GPU_USAGE_DYNAMIC; + ssbo_ = GPU_vertbuf_create_with_format_ex(&format, usage); + GPU_vertbuf_data_alloc(ssbo_, divide_ceil_u(sizeof(T) * this->len_, 4)); + if (!device_only) { + this->data_ = (T *)GPU_vertbuf_get_data(ssbo_); + GPU_vertbuf_use(ssbo_); + } + } +}; + +} // namespace detail + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Uniform Buffers + * \{ */ + +template< + /** Type of the values stored in this uniform buffer. */ + typename T, + /** The number of values that can be stored in this uniform buffer. */ + int64_t len + /** True if the buffer only resides on GPU memory and cannot be accessed. */ + /* TODO(fclem): Currently unsupported. */ + /* bool device_only = false */> +class UniformArrayBuffer : public detail::UniformCommon<T, len, false> { + public: + UniformArrayBuffer() + { + /* TODO(fclem) We should map memory instead. */ + this->data_ = MEM_mallocN_aligned(this->name_); + } +}; + +template< + /** Type of the values stored in this uniform buffer. */ + typename T + /** True if the buffer only resides on GPU memory and cannot be accessed. */ + /* TODO(fclem): Currently unsupported. */ + /* bool device_only = false */> +class UniformBuffer : public T, public detail::UniformCommon<T, 1, false> { + public: + UniformBuffer() + { + /* TODO(fclem) How could we map this? */ + this->data_ = static_cast<T *>(this); + } + + UniformBuffer<T> &operator=(const T &other) + { + *static_cast<T *>(this) = other; + return *this; + } +}; + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Storage Buffer + * \{ */ + +template< + /** Type of the values stored in this uniform buffer. */ + typename T, + /** The number of values that can be stored in this uniform buffer. */ + int64_t len, + /** True if created on device and no memory host memory is allocated. */ + bool device_only = false> +class StorageArrayBuffer : public detail::StorageCommon<T, len, device_only> { + public: + void push_update(void) + { + BLI_assert(!device_only); + /* Get the data again to tag for update. The actual pointer should not + * change. */ + this->data_ = (T *)GPU_vertbuf_get_data(this->ssbo_); + GPU_vertbuf_use(this->ssbo_); + } +}; + +template< + /** Type of the values stored in this uniform buffer. */ + typename T, + /** True if created on device and no memory host memory is allocated. */ + bool device_only = false> +class StorageBuffer : public T, public detail::StorageCommon<T, 1, device_only> { + public: + void push_update(void) + { + BLI_assert(!device_only); + /* TODO(fclem): Avoid a full copy. */ + T &vert_data = *(T *)GPU_vertbuf_get_data(this->ssbo_); + vert_data = *this; + + GPU_vertbuf_use(this->ssbo_); + } + + StorageBuffer<T> &operator=(const T &other) + { + *static_cast<T *>(this) = other; + return *this; + } +}; + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Texture + * \{ */ + +class Texture : NonCopyable { + protected: + GPUTexture *tx_ = nullptr; + const char *name_; + + public: + Texture(const char *name = "gpu::Texture") : name_(name) + { + } + + Texture(const char *name, + eGPUTextureFormat format, + int extent, + float *data = nullptr, + bool cubemap = false, + int mips = 1) + : name_(name) + { + tx_ = create(extent, 0, 0, mips, format, data, false, cubemap); + } + + Texture(const char *name, + eGPUTextureFormat format, + int extent, + int layers, + float *data = nullptr, + bool cubemap = false, + int mips = 1) + : name_(name) + { + tx_ = create(extent, layers, 0, mips, format, data, true, cubemap); + } + + Texture( + const char *name, eGPUTextureFormat format, int2 extent, float *data = nullptr, int mips = 1) + : name_(name) + { + tx_ = create(UNPACK2(extent), 0, mips, format, data, false, false); + } + + Texture(const char *name, + eGPUTextureFormat format, + int2 extent, + int layers, + float *data = nullptr, + int mips = 1) + : name_(name) + { + tx_ = create(UNPACK2(extent), layers, mips, format, data, true, false); + } + + Texture( + const char *name, eGPUTextureFormat format, int3 extent, float *data = nullptr, int mips = 1) + : name_(name) + { + tx_ = create(UNPACK3(extent), mips, format, data, false, false); + } + + ~Texture() + { + free(); + } + + /* To be able to use it with DRW_shgroup_uniform_texture(). */ + operator GPUTexture *() const + { + BLI_assert(tx_ != nullptr); + return tx_; + } + + /* To be able to use it with DRW_shgroup_uniform_texture_ref(). */ + GPUTexture **operator&() + { + return &tx_; + } + + Texture &operator=(Texture &&a) + { + if (*this != a) { + this->tx_ = a.tx_; + this->name_ = a.name_; + a.tx_ = nullptr; + } + return *this; + } + + /** + * Ensure the texture has the correct properties. Recreating it if needed. + * Return true if a texture has been created. + */ + bool ensure_1d(eGPUTextureFormat format, int extent, float *data = nullptr, int mips = 1) + { + return ensure_impl(extent, 0, 0, mips, format, data, false, false); + } + + /** + * Ensure the texture has the correct properties. Recreating it if needed. + * Return true if a texture has been created. + */ + bool ensure_1d_array( + eGPUTextureFormat format, int extent, int layers, float *data = nullptr, int mips = 1) + { + return ensure_impl(extent, layers, 0, mips, format, data, true, false); + } + + /** + * Ensure the texture has the correct properties. Recreating it if needed. + * Return true if a texture has been created. + */ + bool ensure_2d(eGPUTextureFormat format, const int2 &extent, float *data = nullptr, int mips = 1) + { + return ensure_impl(UNPACK2(extent), 0, mips, format, data, false, false); + } + + /** + * Ensure the texture has the correct properties. Recreating it if needed. + * Return true if a texture has been created. + */ + bool ensure_2d_array(eGPUTextureFormat format, + const int2 &extent, + int layers, + float *data = nullptr, + int mips = 1) + { + return ensure_impl(UNPACK2(extent), layers, mips, format, data, true, false); + } + + /** + * Ensure the texture has the correct properties. Recreating it if needed. + * Return true if a texture has been created. + */ + bool ensure_3d(eGPUTextureFormat format, const int3 &extent, float *data = nullptr, int mips = 1) + { + return ensure_impl(UNPACK3(extent), mips, format, data, false, false); + } + + /** + * Ensure the texture has the correct properties. Recreating it if needed. + * Return true if a texture has been created. + */ + bool ensure_cube(eGPUTextureFormat format, int extent, float *data = nullptr, int mips = 1) + { + return ensure_impl(extent, extent, 0, mips, format, data, false, true); + } + + /** + * Ensure the texture has the correct properties. Recreating it if needed. + * Return true if a texture has been created. + */ + bool ensure_cube_array( + eGPUTextureFormat format, int extent, int layers, float *data = nullptr, int mips = 1) + { + return ensure_impl(extent, extent, layers, mips, format, data, false, true); + } + + /** + * Returns true if the texture has been allocated or acquired from the pool. + */ + bool is_valid(void) const + { + return tx_ != nullptr; + } + + int width(void) const + { + return GPU_texture_width(tx_); + } + + int height(void) const + { + return GPU_texture_height(tx_); + } + + bool depth(void) const + { + return GPU_texture_depth(tx_); + } + + bool is_stencil(void) const + { + return GPU_texture_stencil(tx_); + } + + bool is_integer(void) const + { + return GPU_texture_integer(tx_); + } + + bool is_cube(void) const + { + return GPU_texture_cube(tx_); + } + + bool is_array(void) const + { + return GPU_texture_array(tx_); + } + + int3 size(int miplvl = 0) const + { + int3 size(0); + GPU_texture_get_mipmap_size(tx_, miplvl, size); + return size; + } + + /** + * Clear the entirety of the texture using one pixel worth of data. + */ + void clear(float4 values) + { + GPU_texture_clear(tx_, GPU_DATA_FLOAT, &values[0]); + } + + /** + * Clear the entirety of the texture using one pixel worth of data. + */ + void clear(uint4 values) + { + GPU_texture_clear(tx_, GPU_DATA_UINT, &values[0]); + } + + /** + * Clear the entirety of the texture using one pixel worth of data. + */ + void clear(uchar4 values) + { + GPU_texture_clear(tx_, GPU_DATA_UBYTE, &values[0]); + } + + /** + * Clear the entirety of the texture using one pixel worth of data. + */ + void clear(int4 values) + { + GPU_texture_clear(tx_, GPU_DATA_INT, &values[0]); + } + + /** + * Returns a buffer containing the texture data for the specified miplvl. + * The memory block needs to be manually freed by MEM_freeN(). + */ + template<typename T> T *read(eGPUDataFormat format, int miplvl = 0) + { + return reinterpret_cast<T *>(GPU_texture_read(tx_, format, miplvl)); + } + + void filter_mode(bool do_filter) + { + GPU_texture_filter_mode(tx_, do_filter); + } + + /** + * Free the internal texture but not the drw::Texture itself. + */ + void free() + { + GPU_TEXTURE_FREE_SAFE(tx_); + } + + private: + bool ensure_impl(int w, + int h = 0, + int d = 0, + int mips = 1, + eGPUTextureFormat format = GPU_RGBA8, + float *data = nullptr, + bool layered = false, + bool cubemap = false) + + { + /* TODO(fclem) In the future, we need to check if mip_count did not change. + * For now it's ok as we always define all mip level.*/ + if (tx_) { + int3 size = this->size(); + if (size != int3(w, h, d) || GPU_texture_format(tx_) != format || + GPU_texture_cube(tx_) != cubemap || GPU_texture_array(tx_) != layered) { + GPU_TEXTURE_FREE_SAFE(tx_); + } + } + if (tx_ == nullptr) { + tx_ = create(w, h, d, mips, format, data, layered, cubemap); + if (mips > 1) { + /* TODO(fclem) Remove once we have immutable storage or when mips are + * generated on creation. */ + GPU_texture_generate_mipmap(tx_); + } + return true; + } + return false; + } + + GPUTexture *create(int w, + int h, + int d, + int mips, + eGPUTextureFormat format, + float *data, + bool layered, + bool cubemap) + { + if (h == 0) { + return GPU_texture_create_1d(name_, w, mips, format, data); + } + else if (d == 0) { + if (layered) { + return GPU_texture_create_1d_array(name_, w, h, mips, format, data); + } + else { + return GPU_texture_create_2d(name_, w, h, mips, format, data); + } + } + else if (cubemap) { + if (layered) { + return GPU_texture_create_cube_array(name_, w, d, mips, format, data); + } + else { + return GPU_texture_create_cube(name_, w, mips, format, data); + } + } + else { + if (layered) { + return GPU_texture_create_2d_array(name_, w, h, d, mips, format, data); + } + else { + return GPU_texture_create_3d(name_, w, h, d, mips, format, GPU_DATA_FLOAT, data); + } + } + } +}; + +class TextureFromPool : public Texture, NonMovable { + private: + GPUTexture *tx_tmp_saved_ = nullptr; + + public: + TextureFromPool(const char *name = "gpu::Texture") : Texture(name){}; + + /* Always use `release()` after rendering. */ + void acquire(int w, int h, eGPUTextureFormat format, void *owner_) + { + if (this->tx_ == nullptr) { + if (tx_tmp_saved_ != nullptr) { + this->tx_ = tx_tmp_saved_; + return; + } + DrawEngineType *owner = (DrawEngineType *)owner_; + this->tx_ = DRW_texture_pool_query_2d(w, h, format, owner); + } + } + + void release(void) + { + tx_tmp_saved_ = this->tx_; + this->tx_ = nullptr; + } + + /** + * Clears any reference. Workaround for pool texture not being able to release on demand. + * Needs to be called at during the sync phase. + */ + void sync(void) + { + tx_tmp_saved_ = nullptr; + } + + /** Remove methods that are forbidden with this type of textures. */ + bool ensure_1d(int, int, eGPUTextureFormat, float *) = delete; + bool ensure_1d_array(int, int, int, eGPUTextureFormat, float *) = delete; + bool ensure_2d(int, int, int, eGPUTextureFormat, float *) = delete; + bool ensure_2d_array(int, int, int, int, eGPUTextureFormat, float *) = delete; + bool ensure_3d(int, int, int, int, eGPUTextureFormat, float *) = delete; + bool ensure_cube(int, int, eGPUTextureFormat, float *) = delete; + bool ensure_cube_array(int, int, int, eGPUTextureFormat, float *) = delete; + void filter_mode(bool) = delete; + void free() = delete; + /** + * Forbid the use of DRW_shgroup_uniform_texture. + * Use DRW_shgroup_uniform_texture_ref instead. + */ + operator GPUTexture *() const = delete; +}; + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Framebuffer + * \{ */ + +class Framebuffer : NonCopyable { + private: + GPUFrameBuffer *fb_ = nullptr; + const char *name_; + + public: + Framebuffer() : name_(""){}; + Framebuffer(const char *name) : name_(name){}; + + ~Framebuffer() + { + GPU_FRAMEBUFFER_FREE_SAFE(fb_); + } + + void ensure(GPUAttachment depth = GPU_ATTACHMENT_NONE, + GPUAttachment color1 = GPU_ATTACHMENT_NONE, + GPUAttachment color2 = GPU_ATTACHMENT_NONE, + GPUAttachment color3 = GPU_ATTACHMENT_NONE, + GPUAttachment color4 = GPU_ATTACHMENT_NONE, + GPUAttachment color5 = GPU_ATTACHMENT_NONE, + GPUAttachment color6 = GPU_ATTACHMENT_NONE, + GPUAttachment color7 = GPU_ATTACHMENT_NONE, + GPUAttachment color8 = GPU_ATTACHMENT_NONE) + { + GPU_framebuffer_ensure_config( + &fb_, {depth, color1, color2, color3, color4, color5, color6, color7, color8}); + } + + Framebuffer &operator=(Framebuffer &&a) + { + if (*this != a) { + this->fb_ = a.fb_; + this->name_ = a.name_; + a.fb_ = nullptr; + } + return *this; + } + + operator GPUFrameBuffer *() const + { + return fb_; + } +}; + +/** \} */ + +} // namespace blender::draw |