mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-19 00:41:43 +00:00
[wpigui] Refactor texture handling
The platform-specific code now only has create, update, and delete texture. Image reading functions have been moved to common code. Also add pixel data functions and image data functions in addition to image file loading.
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
#include <imgui_impl_glfw.h>
|
||||
#include <imgui_internal.h>
|
||||
#include <implot.h>
|
||||
#include <stb_image.h>
|
||||
|
||||
#include "wpigui_internal.h"
|
||||
|
||||
@@ -271,7 +272,10 @@ void gui::CommonRenderFrame() {
|
||||
ImGui::Render();
|
||||
}
|
||||
|
||||
void gui::Exit() { gContext->exit = true; }
|
||||
void gui::Exit() {
|
||||
if (!gContext) return;
|
||||
gContext->exit = true;
|
||||
}
|
||||
|
||||
void gui::AddInit(std::function<void()> initialize) {
|
||||
if (initialize) gContext->initializers.emplace_back(std::move(initialize));
|
||||
@@ -354,4 +358,59 @@ void gui::EmitViewMenu() {
|
||||
}
|
||||
}
|
||||
|
||||
bool gui::UpdateTextureFromImage(ImTextureID* texture, int width, int height,
|
||||
const unsigned char* data, int len) {
|
||||
// Load from memory
|
||||
int width2 = 0;
|
||||
int height2 = 0;
|
||||
unsigned char* imgData =
|
||||
stbi_load_from_memory(data, len, &width2, &height2, nullptr, 4);
|
||||
if (!data) return false;
|
||||
|
||||
if (width2 == width && height2 == height)
|
||||
UpdateTexture(texture, kPixelRGBA, width2, height2, imgData);
|
||||
else
|
||||
*texture = CreateTexture(kPixelRGBA, width2, height2, imgData);
|
||||
|
||||
stbi_image_free(imgData);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool gui::CreateTextureFromFile(const char* filename, ImTextureID* out_texture,
|
||||
int* out_width, int* out_height) {
|
||||
// Load from file
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
unsigned char* data = stbi_load(filename, &width, &height, nullptr, 4);
|
||||
if (!data) return false;
|
||||
|
||||
*out_texture = CreateTexture(kPixelRGBA, width, height, data);
|
||||
if (out_width) *out_width = width;
|
||||
if (out_height) *out_height = height;
|
||||
|
||||
stbi_image_free(data);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool gui::CreateTextureFromImage(const unsigned char* data, int len,
|
||||
ImTextureID* out_texture, int* out_width,
|
||||
int* out_height) {
|
||||
// Load from memory
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
unsigned char* imgData =
|
||||
stbi_load_from_memory(data, len, &width, &height, nullptr, 4);
|
||||
if (!imgData) return false;
|
||||
|
||||
*out_texture = CreateTexture(kPixelRGBA, width, height, imgData);
|
||||
if (out_width) *out_width = width;
|
||||
if (out_height) *out_height = height;
|
||||
|
||||
stbi_image_free(imgData);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace wpi
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
#include <imgui.h>
|
||||
#include <imgui_impl_glfw.h>
|
||||
#include <imgui_impl_dx11.h>
|
||||
#include <stb_image.h>
|
||||
|
||||
#include "wpigui.h"
|
||||
#include "wpigui_internal.h"
|
||||
@@ -32,6 +31,7 @@ struct PlatformContext {
|
||||
} // namespace
|
||||
|
||||
static PlatformContext* gPlatformContext;
|
||||
static bool gPlatformValid = false;
|
||||
|
||||
static void CreateRenderTarget() {
|
||||
ID3D11Texture2D* pBackBuffer;
|
||||
@@ -127,6 +127,7 @@ bool gui::PlatformInitRenderer() {
|
||||
ImGui_ImplDX11_Init(gPlatformContext->pd3dDevice,
|
||||
gPlatformContext->pd3dDeviceContext);
|
||||
|
||||
gPlatformValid = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -146,15 +147,25 @@ void gui::PlatformRenderFrame() {
|
||||
// gPlatformContext->pSwapChain->Present(0, 0); // Present without vsync
|
||||
}
|
||||
|
||||
void gui::PlatformShutdown() { ImGui_ImplDX11_Shutdown(); }
|
||||
void gui::PlatformShutdown() {
|
||||
gPlatformValid = false;
|
||||
ImGui_ImplDX11_Shutdown();
|
||||
}
|
||||
|
||||
bool gui::LoadTextureFromFile(const char* filename, ImTextureID* out_texture,
|
||||
int* out_width, int* out_height) {
|
||||
// Load from disk into a raw RGBA buffer
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
unsigned char* data = stbi_load(filename, &width, &height, nullptr, 4);
|
||||
if (!data) return false;
|
||||
static inline DXGI_FORMAT DXPixelFormat(PixelFormat format) {
|
||||
switch (format) {
|
||||
case kPixelRGBA:
|
||||
return DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
case kPixelBGRA:
|
||||
return DXGI_FORMAT_B8G8R8A8_UNORM;
|
||||
default:
|
||||
return DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
}
|
||||
}
|
||||
|
||||
ImTextureID gui::CreateTexture(PixelFormat format, int width, int height,
|
||||
const unsigned char* data) {
|
||||
if (!gPlatformValid) return nullptr;
|
||||
|
||||
// Create texture
|
||||
D3D11_TEXTURE2D_DESC desc;
|
||||
@@ -188,15 +199,34 @@ bool gui::LoadTextureFromFile(const char* filename, ImTextureID* out_texture,
|
||||
&srv);
|
||||
pTexture->Release();
|
||||
|
||||
*out_texture = srv;
|
||||
*out_width = width;
|
||||
*out_height = height;
|
||||
stbi_image_free(data);
|
||||
return srv;
|
||||
}
|
||||
|
||||
return true;
|
||||
void gui::UpdateTexture(ImTextureID texture, PixelFormat, int width, int height,
|
||||
const unsigned char* data) {
|
||||
if (!texture) return;
|
||||
|
||||
D3D11_BOX box;
|
||||
box.front = 0;
|
||||
box.back = 1;
|
||||
box.left = 0;
|
||||
box.right = width;
|
||||
box.top = 0;
|
||||
box.bottom = height;
|
||||
|
||||
ID3D11Resource* resource = nullptr;
|
||||
static_cast<ID3D11ShaderResourceView*>(texture)->GetResource(&resource);
|
||||
|
||||
if (resource) {
|
||||
gPlatformContext->pd3dDeviceContext->UpdateSubresource(
|
||||
resource, 0, &box, data, width * 4, width * height * 4);
|
||||
|
||||
resource->Release();
|
||||
}
|
||||
}
|
||||
|
||||
void gui::DeleteTexture(ImTextureID texture) {
|
||||
if (!gPlatformValid) return;
|
||||
if (texture) static_cast<ID3D11ShaderResourceView*>(texture)->Release();
|
||||
}
|
||||
|
||||
|
||||
@@ -130,7 +130,56 @@ void SetClearColor(ImVec4 color);
|
||||
void EmitViewMenu();
|
||||
|
||||
/**
|
||||
* Loads a texture from a file.
|
||||
* Pixel formats for texture pixel data.
|
||||
*/
|
||||
enum PixelFormat { kPixelRGBA, kPixelBGRA };
|
||||
|
||||
/**
|
||||
* Creates a texture from pixel data.
|
||||
*
|
||||
* @param format pixel format
|
||||
* @param width image width
|
||||
* @param height image height
|
||||
* @param data pixel data
|
||||
* @return Texture
|
||||
*/
|
||||
ImTextureID CreateTexture(PixelFormat format, int width, int height,
|
||||
const unsigned char* data);
|
||||
|
||||
/**
|
||||
* Updates a texture from pixel data.
|
||||
* The passed-in width and height must match the width and height of the
|
||||
* texture.
|
||||
*
|
||||
* @param texture texture
|
||||
* @param format pixel format
|
||||
* @param width texture width
|
||||
* @param height texture height
|
||||
* @param data pixel data
|
||||
*/
|
||||
void UpdateTexture(ImTextureID texture, PixelFormat format, int width,
|
||||
int height, const unsigned char* data);
|
||||
|
||||
/**
|
||||
* Updates a texture from image data.
|
||||
* The pixel format of the texture must be RGBA. The passed-in width and
|
||||
* height must match the width and height of the texture. If the width and
|
||||
* height of the image differ from the passed-in width and height, a new
|
||||
* texture is created (note this may be inefficient).
|
||||
*
|
||||
* @param texture texture (pointer, may be updated)
|
||||
* @param width texture width
|
||||
* @param height texture height
|
||||
* @param data image data
|
||||
* @param len image data length
|
||||
*
|
||||
* @return True on success, false on failure.
|
||||
*/
|
||||
bool UpdateTextureFromImage(ImTextureID* texture, int width, int height,
|
||||
const unsigned char* data, int len);
|
||||
|
||||
/**
|
||||
* Creates a texture from an image file.
|
||||
*
|
||||
* @param filename filename
|
||||
* @param out_texture texture (output)
|
||||
@@ -138,8 +187,22 @@ void EmitViewMenu();
|
||||
* @param out_height image height (output)
|
||||
* @return True on success, false on failure.
|
||||
*/
|
||||
bool LoadTextureFromFile(const char* filename, ImTextureID* out_texture,
|
||||
int* out_width, int* out_height);
|
||||
bool CreateTextureFromFile(const char* filename, ImTextureID* out_texture,
|
||||
int* out_width, int* out_height);
|
||||
|
||||
/**
|
||||
* Creates a texture from image data.
|
||||
*
|
||||
* @param data image data
|
||||
* @param len image data length
|
||||
* @param out_texture texture (output)
|
||||
* @param out_width image width (output)
|
||||
* @param out_height image height (output)
|
||||
* @return True on success, false on failure.
|
||||
*/
|
||||
bool CreateTextureFromImage(const unsigned char* data, int len,
|
||||
ImTextureID* out_texture, int* out_width,
|
||||
int* out_height);
|
||||
|
||||
/**
|
||||
* Deletes a texture.
|
||||
@@ -148,4 +211,144 @@ bool LoadTextureFromFile(const char* filename, ImTextureID* out_texture,
|
||||
*/
|
||||
void DeleteTexture(ImTextureID texture);
|
||||
|
||||
/**
|
||||
* RAII wrapper around ImTextureID. Also keeps track of width, height, and
|
||||
* pixel format.
|
||||
*/
|
||||
class Texture {
|
||||
public:
|
||||
Texture() = default;
|
||||
|
||||
/**
|
||||
* Constructs a texture from pixel data.
|
||||
*
|
||||
* @param format pixel format
|
||||
* @param width image width
|
||||
* @param height image height
|
||||
* @param data pixel data
|
||||
*/
|
||||
Texture(PixelFormat format, int width, int height, const unsigned char* data)
|
||||
: m_format{format}, m_width{width}, m_height{height} {
|
||||
m_texture = CreateTexture(format, width, height, data);
|
||||
}
|
||||
|
||||
Texture(const Texture&) = delete;
|
||||
Texture(Texture&& oth)
|
||||
: m_texture{oth.m_texture},
|
||||
m_format{oth.m_format},
|
||||
m_width{oth.m_width},
|
||||
m_height{oth.m_height} {
|
||||
oth.m_texture = 0;
|
||||
}
|
||||
|
||||
Texture& operator=(const Texture&) = delete;
|
||||
Texture& operator=(Texture&& oth) {
|
||||
if (m_texture) DeleteTexture(m_texture);
|
||||
m_texture = oth.m_texture;
|
||||
oth.m_texture = 0;
|
||||
m_format = oth.m_format;
|
||||
m_width = oth.m_width;
|
||||
m_height = oth.m_height;
|
||||
return *this;
|
||||
}
|
||||
|
||||
~Texture() {
|
||||
if (m_texture) DeleteTexture(m_texture);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates to true if the texture is valid.
|
||||
*/
|
||||
explicit operator bool() const { return m_texture; }
|
||||
|
||||
/**
|
||||
* Implicit conversion to ImTextureID.
|
||||
*/
|
||||
operator ImTextureID() const { return m_texture; }
|
||||
|
||||
/**
|
||||
* Gets the texture pixel format.
|
||||
*
|
||||
* @return pixel format
|
||||
*/
|
||||
PixelFormat GetFormat() const { return m_format; }
|
||||
|
||||
/**
|
||||
* Gets the texture width.
|
||||
*
|
||||
* @return width
|
||||
*/
|
||||
int GetWidth() const { return m_width; }
|
||||
|
||||
/**
|
||||
* Gets the texture height.
|
||||
*
|
||||
* @return height
|
||||
*/
|
||||
int GetHeight() const { return m_height; }
|
||||
|
||||
/**
|
||||
* Updates the texture from pixel data.
|
||||
* The image data size and format is assumed to match that of the texture.
|
||||
*
|
||||
* @param format pixel format
|
||||
* @param data pixel data
|
||||
*/
|
||||
void Update(const unsigned char* data) {
|
||||
UpdateTexture(m_texture, m_format, m_width, m_height, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the texture from image data.
|
||||
* The pixel format of the texture must be RGBA. If the width and height of
|
||||
* the image differ from the texture width and height, a new texture is
|
||||
* created (note this may be inefficient).
|
||||
*
|
||||
* @param data image data
|
||||
* @param len image data length
|
||||
*
|
||||
* @return True on success, false on failure.
|
||||
*/
|
||||
bool UpdateFromImage(const unsigned char* data, int len) {
|
||||
return UpdateTextureFromImage(&m_texture, m_width, m_height, data, len);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a texture by loading an image file.
|
||||
*
|
||||
* @param filename filename
|
||||
*
|
||||
* @return Texture, or invalid (empty) texture on failure.
|
||||
*/
|
||||
static Texture CreateFromFile(const char* filename) {
|
||||
Texture texture;
|
||||
if (!CreateTextureFromFile(filename, &texture.m_texture, &texture.m_width,
|
||||
&texture.m_height))
|
||||
return {};
|
||||
return texture;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a texture from image data.
|
||||
*
|
||||
* @param data image data
|
||||
* @param len image data length
|
||||
*
|
||||
* @return Texture, or invalid (empty) texture on failure.
|
||||
*/
|
||||
static Texture CreateFromImage(const unsigned char* data, int len) {
|
||||
Texture texture;
|
||||
if (!CreateTextureFromImage(data, len, &texture.m_texture, &texture.m_width,
|
||||
&texture.m_height))
|
||||
return {};
|
||||
return texture;
|
||||
}
|
||||
|
||||
private:
|
||||
ImTextureID m_texture = nullptr;
|
||||
PixelFormat m_format = kPixelRGBA;
|
||||
int m_width = 0;
|
||||
int m_height = 0;
|
||||
};
|
||||
|
||||
} // namespace wpi::gui
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
#include <imgui.h>
|
||||
#include <imgui_impl_glfw.h>
|
||||
#include <imgui_impl_metal.h>
|
||||
#include <stb_image.h>
|
||||
|
||||
#include "wpigui.h"
|
||||
#include "wpigui_internal.h"
|
||||
@@ -32,6 +31,7 @@ struct PlatformContext {
|
||||
} // namespace
|
||||
|
||||
static PlatformContext* gPlatformContext;
|
||||
static bool gPlatformValid = false;
|
||||
|
||||
namespace wpi {
|
||||
|
||||
@@ -66,6 +66,7 @@ bool gui::PlatformInitRenderer() {
|
||||
|
||||
gPlatformContext->renderPassDescriptor = [MTLRenderPassDescriptor new];
|
||||
|
||||
gPlatformValid = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -103,17 +104,26 @@ void gui::PlatformRenderFrame() {
|
||||
|
||||
void gui::PlatformShutdown() {
|
||||
ImGui_ImplMetal_Shutdown();
|
||||
gPlatformValid = false;
|
||||
}
|
||||
|
||||
bool gui::LoadTextureFromFile(const char* filename, ImTextureID* out_texture,
|
||||
int* out_width, int* out_height) {
|
||||
// Load from file
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
unsigned char* data = stbi_load(filename, &width, &height, nullptr, 4);
|
||||
if (!data) return false;
|
||||
static inline MTLPixelFormat MetalPixelFormat(PixelFormat format) {
|
||||
switch (format) {
|
||||
case kPixelRGBA:
|
||||
return MTLPixelFormatRGBA8Unorm;
|
||||
case kPixelBGRA:
|
||||
return MTLPixelFormatBGRA8Unorm;
|
||||
default:
|
||||
return MTLPixelFormatRGBA8Unorm;
|
||||
}
|
||||
}
|
||||
|
||||
MTLTextureDescriptor *textureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm width:width height:height mipmapped:NO];
|
||||
ImTextureID gui::CreateTexture(PixelFormat format, int width, int height,
|
||||
const unsigned char* data) {
|
||||
if (!gPlatformValid) return nullptr;
|
||||
|
||||
MTLPixelFormat fmt = MetalPixelFormat(format);
|
||||
MTLTextureDescriptor *textureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:fmt width:width height:height mipmapped:NO];
|
||||
textureDescriptor.usage = MTLTextureUsageShaderRead;
|
||||
#if TARGET_OS_OSX
|
||||
textureDescriptor.storageMode = MTLStorageModeManaged;
|
||||
@@ -123,16 +133,18 @@ bool gui::LoadTextureFromFile(const char* filename, ImTextureID* out_texture,
|
||||
id <MTLTexture> texture = [gPlatformContext->layer.device newTextureWithDescriptor:textureDescriptor];
|
||||
[texture replaceRegion:MTLRegionMake2D(0, 0, width, height) mipmapLevel:0 withBytes:data bytesPerRow:width * 4];
|
||||
|
||||
*out_texture = (__bridge_retained void *)texture;
|
||||
*out_width = width;
|
||||
*out_height = height;
|
||||
stbi_image_free(data);
|
||||
return (__bridge_retained void *)texture;
|
||||
}
|
||||
|
||||
return true;
|
||||
void gui::UpdateTexture(ImTextureID texture, PixelFormat, int width,
|
||||
int height, const unsigned char* data) {
|
||||
if (!texture) return;
|
||||
id <MTLTexture> mtlTexture = (__bridge id <MTLTexture>)texture;
|
||||
[mtlTexture replaceRegion:MTLRegionMake2D(0, 0, width, height) mipmapLevel:0 withBytes:data bytesPerRow:width * 4];
|
||||
}
|
||||
|
||||
void gui::DeleteTexture(ImTextureID texture) {
|
||||
if (!texture) return;
|
||||
if (!gPlatformValid || !texture) return;
|
||||
id <MTLTexture> mtlTexture = (__bridge_transfer id <MTLTexture>)texture;
|
||||
(void)mtlTexture;
|
||||
}
|
||||
|
||||
@@ -12,13 +12,14 @@
|
||||
#include <imgui.h>
|
||||
#include <imgui_impl_glfw.h>
|
||||
#include <imgui_impl_opengl3.h>
|
||||
#include <stb_image.h>
|
||||
|
||||
#include "wpigui.h"
|
||||
#include "wpigui_internal.h"
|
||||
|
||||
using namespace wpi::gui;
|
||||
|
||||
static bool gPlatformValid = false;
|
||||
|
||||
namespace wpi {
|
||||
|
||||
void gui::PlatformCreateContext() {}
|
||||
@@ -73,6 +74,7 @@ bool gui::PlatformInitRenderer() {
|
||||
#endif
|
||||
ImGui_ImplOpenGL3_Init(glsl_version);
|
||||
|
||||
gPlatformValid = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -92,15 +94,25 @@ void gui::PlatformRenderFrame() {
|
||||
glfwSwapBuffers(gContext->window);
|
||||
}
|
||||
|
||||
void gui::PlatformShutdown() { ImGui_ImplOpenGL3_Shutdown(); }
|
||||
void gui::PlatformShutdown() {
|
||||
gPlatformValid = false;
|
||||
ImGui_ImplOpenGL3_Shutdown();
|
||||
}
|
||||
|
||||
bool gui::LoadTextureFromFile(const char* filename, ImTextureID* out_texture,
|
||||
int* out_width, int* out_height) {
|
||||
// Load from file
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
unsigned char* data = stbi_load(filename, &width, &height, nullptr, 4);
|
||||
if (!data) return false;
|
||||
static inline GLenum GLPixelFormat(PixelFormat format) {
|
||||
switch (format) {
|
||||
case kPixelRGBA:
|
||||
return GL_RGBA;
|
||||
case kPixelBGRA:
|
||||
return GL_BGRA;
|
||||
default:
|
||||
return GL_RGBA;
|
||||
}
|
||||
}
|
||||
|
||||
ImTextureID gui::CreateTexture(PixelFormat format, int width, int height,
|
||||
const unsigned char* data) {
|
||||
if (!gPlatformValid) return nullptr;
|
||||
|
||||
// Create a OpenGL texture identifier
|
||||
GLuint texture;
|
||||
@@ -113,18 +125,23 @@ bool gui::LoadTextureFromFile(const char* filename, ImTextureID* out_texture,
|
||||
|
||||
// Upload pixels into texture
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA,
|
||||
GL_UNSIGNED_BYTE, data);
|
||||
stbi_image_free(data);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
|
||||
GLPixelFormat(format), GL_UNSIGNED_BYTE, data);
|
||||
|
||||
*out_texture = reinterpret_cast<ImTextureID>(static_cast<uintptr_t>(texture));
|
||||
if (out_width) *out_width = width;
|
||||
if (out_height) *out_height = height;
|
||||
return reinterpret_cast<ImTextureID>(static_cast<uintptr_t>(texture));
|
||||
}
|
||||
|
||||
return true;
|
||||
void gui::UpdateTexture(ImTextureID texture, PixelFormat format, int width,
|
||||
int height, const unsigned char* data) {
|
||||
GLuint glTexture = static_cast<GLuint>(reinterpret_cast<uintptr_t>(texture));
|
||||
if (glTexture == 0) return;
|
||||
glBindTexture(GL_TEXTURE_2D, glTexture);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GLPixelFormat(format),
|
||||
GL_UNSIGNED_BYTE, data);
|
||||
}
|
||||
|
||||
void gui::DeleteTexture(ImTextureID texture) {
|
||||
if (!gPlatformValid) return;
|
||||
GLuint glTexture = static_cast<GLuint>(reinterpret_cast<uintptr_t>(texture));
|
||||
if (glTexture != 0) glDeleteTextures(1, &glTexture);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user