Updated Image Loading and Displaying Examples (markdown)

omar 2020-05-22 13:11:21 +02:00
parent e17bc101e4
commit 54da8022c0

@ -1,5 +1,13 @@
Examples: [OpenGL](#Example-for-OpenGL-users) - [DirectX9](#Example-for-DirectX9-users) - [DirectX11](#Example-for-DirectX11-users) - [DirectX11](#Example-for-DirectX12-users)
<br>More: [About Texture Coordinates](#About-texture-coordinates)
## Index
- [TL;DR;](#tl-dr)
- [About filenames](#about-filenames)
- [About ImTextureId](#about-imtextureid)
- [Example for OpenGL users](#Example-for-OpenGL-users)
- [Example for DirectX9 users](#Example-for-DirectX9-users)
- [Example for DirectX11 users](#Example-for-DirectX11-users)
- [Example for DirectX12 users](#Example-for-DirectX12-users)
- [More: About Texture Coordinates](#About-texture-coordinates)
## TL;DR;
@ -18,19 +26,65 @@ Once you have an image in GPU texture memory, you can use functions such as `ImG
(Note: Large games and applications are likely to be using texture formats that are compressed on the GPU or more advanced techniques that are outside of the scope of this article. Generally, if you are reading this you shouldn't need to worry or care about that.)
##### [Return to Index](#index)
## About filenames
**Please note that many new C/C++ users have issues their files _because the filename they provide is wrong_.**
Two things to watch for:
- Make sure your IDE/debugger settings starts your executable from the right working directory. In Visual Studio you can change your working directory in project `Properties > General > Debugging > Working Directory`. People assume that their execution will start from the root folder of the project, where by default it oftens start from the folder where object or executable files are stored.
- In C/C++ and most programming languages if you want to use a backslash `\` within a string literal, you need to write it double backslash `\\`. At it happens, Windows uses backslashes as a path separator, so be mindful.
```cpp
filename = "MyImage01.jpg" // Relative filename depends on your Working Directory when running your program!
```
"C:\MyFiles\MyImage01.jpg" // This is INCORRECT!!
"C:\\MyFiles\\MyImage01.jpg" // This is CORRECT
- In C/C++ and most programming languages if you want to use a backslash `\` within a string literal, you need to write it double backslash `\\`. At it happens, Windows uses backslashes as a path separator, so be mindful.
```cpp
filename = "C:\MyFiles\MyImage01.jpg" // This is INCORRECT!!
filename = "C:\\MyFiles\\MyImage01.jpg" // This is CORRECT
```
In some situations, you may also use `/` path separator under Windows.
##### [Return to Index](#index)
## About ImTextureId
From the FAQ: "How can I display an image? What is ImTextureID, how does it works?"
Short explanation:
- You may use functions such as `ImGui::Image()`, `ImGui::ImageButton()` or lower-level `ImDrawList::AddImage()` to emit draw calls that will use your own textures.
- Actual textures are identified in a way that is up to the user/engine. Those identifiers are stored and passed as a `ImTextureID` value (which is no other than a `void*`).
- Loading image files from the disk and turning them into a texture is not within the scope of Dear ImGui (for a good reason). You can read documentations or tutorials on your graphics API to understand how to upload textures. Onward in this document you'll find examples.
Long explanation:
- Dear ImGui's job is to create "meshes", defined in a renderer-agnostic format made of draw commands and vertices. At the end of the frame those meshes (ImDrawList) will be displayed by your rendering function. They are made up of textured polygons and the code to render them is generally fairly short (a few dozen lines). In the examples/ folder we provide functions for popular graphics API (OpenGL, DirectX, etc.).
- Each rendering function decides on a data type to represent "textures". The concept of what is a "texture" is entirely tied to your underlying engine/graphics API. We carry the information to identify a "texture" in the ImTextureID type. ImTextureID is nothing more that a void*, aka 4/8 bytes worth of data: just enough to store 1 pointer or 1 integer of your choice. Dear ImGui doesn't know or understand what you are storing in ImTextureID, it merely pass ImTextureID values until they reach your rendering function.
- In the examples/ bindings, for each graphics API binding we decided on a type that is likely to be a good representation for specifying an image from the end-user perspective. This is what the _examples_ rendering functions are using:
```
OpenGL: ImTextureID = GLuint (see ImGui_ImplOpenGL3_RenderDrawData() in imgui_impl_opengl3.cpp)
DirectX9: ImTextureID = LPDIRECT3DTEXTURE9 (see ImGui_ImplDX9_RenderDrawData() in imgui_impl_dx9.cpp)
DirectX11: ImTextureID = ID3D11ShaderResourceView* (see ImGui_ImplDX11_RenderDrawData() in imgui_impl_dx11.cpp)
DirectX12: ImTextureID = D3D12_GPU_DESCRIPTOR_HANDLE (see ImGui_ImplDX12_RenderDrawData() in imgui_impl_dx12.cpp)
```
For example, in the OpenGL example binding we store raw OpenGL texture identifier (GLuint) inside ImTextureID. Whereas in the DirectX11 example binding we store a pointer to ID3D11ShaderResourceView inside ImTextureID, which is a higher-level structure tying together both the texture and information about its format and how to read it.
- If you have a custom engine built over e.g. OpenGL, instead of passing GLuint around you may decide to use a high-level data type to carry information about the texture as well as how to display it (shaders, etc.). The decision of what to use as ImTextureID can always be made better knowing how your codebase is designed. If your engine has high-level data types for "textures" and "material" then you may want to use them. If you are starting with OpenGL or DirectX or Vulkan and haven't built much of a rendering engine over them, keeping the default ImTextureID representation suggested by the example bindings is probably the best choice. (Advanced users may also decide to keep a low-level type in ImTextureID, and use ImDrawList callback and pass information to their renderer)
User code may do:
```cpp
// Cast our texture type to ImTextureID / void*
MyTexture* texture = g_CoffeeTableTexture;
ImGui::Image((void*)texture, ImVec2(texture->Width, texture->Height));
```
The renderer function called after ImGui::Render() will receive that same value that the user code passed:
```cpp
// Cast ImTextureID / void* stored in the draw command as our texture type
MyTexture* texture = (MyTexture*)pcmd->TextureId;
MyEngineBindTexture2D(texture);
```
Once you understand this design you can begin understand that loading image files and turning them into displayable textures is not within the scope of Dear ImGui. This is by design and is actually a good thing, because it means your code has full control over your data types and how you display them. In reality, the concept of what constitute a "texture" is largely open-ended, and Dear ImGui doesn't want to narrow that concept. If you want to display an image file (e.g. PNG file) into the screen, please refer to tutorials above.
Finally, you may call `ImGui::ShowMetricsWindow()` to explore/visualize/understand how the ImDrawList are generated.
##### [Return to Index](#index)
----
## Example for OpenGL users
@ -38,7 +92,7 @@ In some situations, you may also use `/` path separator under Windows.
We will here use [stb_image.h](https://github.com/nothings/stb/blob/master/stb_image.h) to load images from disk.
Add at the top of one of your source file:
```
```cpp
#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>
@ -75,7 +129,7 @@ bool LoadTextureFromFile(const char* filename, GLuint* out_texture, int* out_wid
```
At initialization time, load our texture:
```
```cpp
int my_image_width = 0;
int my_image_height = 0;
GLuint my_image_texture = 0;
@ -86,7 +140,7 @@ IM_ASSERT(ret);
In the snippet of code above, we added an assert `IM_ASSERT(ret)` to check if the image file was loaded correctly. You may also use your Debugger and confirm that `my_image_texture` is not zero and that `my_image_width` `my_image_width` are correct.
Now that we have an OpenGL texture and its dimensions, we can display it in our main loop:
```
```cpp
ImGui::Begin("OpenGL Texture Text");
ImGui::Text("pointer = %p", my_image_texture);
ImGui::Text("size = %d x %d", my_image_width, my_image_height);
@ -96,6 +150,8 @@ ImGui::End();
![image](https://user-images.githubusercontent.com/8225057/65344387-dfc21180-dbd7-11e9-9478-627403721435.png)
##### [Return to Index](#index)
----
## Example for DirectX9 users
@ -103,7 +159,7 @@ ImGui::End();
Unlike the majority of modern graphics API, DirectX9 include helper functions to load image files from disk. We are going to use them here, instead of using `stb_image.h`.
Add at the top of one of your source file:
```
```cpp
#include <D3dx9tex.h>
#pragma comment(lib, "D3dx9")
@ -127,7 +183,7 @@ bool LoadTextureFromFile(const char* filename, PDIRECT3DTEXTURE9* out_texture, i
```
At initialization time, load our texture:
```
```cpp
int my_image_width = 0;
int my_image_height = 0;
PDIRECT3DTEXTURE9 my_texture = NULL;
@ -136,7 +192,7 @@ IM_ASSERT(ret);
```
Now that we have an DirectX9 texture and its dimensions, we can display it in our main loop:
```
```cpp
ImGui::Begin("DirectX9 Texture Test");
ImGui::Text("pointer = %p", my_texture);
ImGui::Text("size = %d x %d", my_image_width, my_image_height);
@ -146,12 +202,14 @@ ImGui::End();
![image](https://user-images.githubusercontent.com/8225057/65344041-dedcb000-dbd6-11e9-8a52-120673e8e85f.png)
##### [Return to Index](#index)
----
## Example for DirectX11 users
Add at the top of one of your source file:
```
```cpp
#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>
@ -204,7 +262,7 @@ bool LoadTextureFromFile(const char* filename, ID3D11ShaderResourceView** out_sr
```
At initialization time, load our texture:
```
```cpp
int my_image_width = 0;
int my_image_height = 0;
ID3D11ShaderResourceView* my_texture = NULL;
@ -213,7 +271,7 @@ IM_ASSERT(ret);
```
Now that we have an DirectX11 texture and its dimensions, we can display it in our main loop:
```
```cpp
ImGui::Begin("DirectX11 Texture Test");
ImGui::Text("pointer = %p", my_texture);
ImGui::Text("size = %d x %d", my_image_width, my_image_height);
@ -223,13 +281,15 @@ ImGui::End();
![image](https://user-images.githubusercontent.com/8225057/65343598-a38db180-dbd5-11e9-8776-80dfa4a7f3c6.png)
##### [Return to Index](#index)
----
## Example for DirectX12 users
It should be noted that the DirectX12 API is substantially more complex than previous iterations, and assumes that you will be writing your own code to manage various things such as resource allocation. As such, it's a lot harder to provide a "one size fits all" example than with some APIs, and in all probability the code below will need modification to work with any existing engine code. It should however work if inserted into the Dear ImGui DirectX12 example project, with the caveat that the `CreateDeviceD3D()` function will need to be modified slightly to allocate two descriptors in the SRV resource descriptor heap as follows:
```C++
```cpp
{
D3D12_DESCRIPTOR_HEAP_DESC desc = {};
desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
@ -243,7 +303,7 @@ It should be noted that the DirectX12 API is substantially more complex than pre
If you want to use this code to load more than one texture (and don't have your own mechanism for allocating descriptors), then you will need to increase `NumDescriptors` accordingly and also increment `descriptor_index` for each texture loaded.
The actual implementation itself is as follows - firstly, at the top of one of your source files add:
```C++
```cpp
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
@ -408,7 +468,7 @@ bool LoadTextureFromFile(const char* filename, ID3D12Device* d3d_device, D3D12_C
```
Then at initialization time, load our texture:
```C++
```cpp
// We need to pass a D3D12_CPU_DESCRIPTOR_HANDLE in ImTextureID, so make sure it will fit
static_assert(sizeof(ImTextureID) >= sizeof(D3D12_CPU_DESCRIPTOR_HANDLE), "D3D12_CPU_DESCRIPTOR_HANDLE is too large to fit in an ImTextureID");
@ -434,7 +494,7 @@ IM_ASSERT(ret);
```
And finally now that we have an DirectX12 texture and its dimensions, we can display it in our main loop:
```C++
```cpp
ImGui::Begin("DirectX12 Texture Test");
ImGui::Text("CPU handle = %p", my_texture_srv_cpu_handle.ptr);
ImGui::Text("GPU handle = %p", my_texture_srv_gpu_handle.ptr);
@ -446,45 +506,10 @@ ImGui::End();
![Sample screenshot](https://user-images.githubusercontent.com/47202645/82640130-05ab2480-9c45-11ea-9488-82b8554db638.jpg)
##### [Return to Index](#index)
----
### Technical Explanation
From the FAQ: "How can I display an image? What is ImTextureID, how does it works?"
Short explanation:
- You may use functions such as `ImGui::Image()`, `ImGui::ImageButton()` or lower-level `ImDrawList::AddImage()` to emit draw calls that will use your own textures.
- Actual textures are identified in a way that is up to the user/engine. Those identifiers are stored and passed as a `ImTextureID` value (which is no other than a `void*`).
- Loading image files from the disk and turning them into a texture is not within the scope of Dear ImGui (for a good reason). You can read documentations or tutorials on your graphics API to understand how to upload textures. Onward in this document you'll find examples.
Long explanation:
- Dear ImGui's job is to create "meshes", defined in a renderer-agnostic format made of draw commands and vertices. At the end of the frame those meshes (ImDrawList) will be displayed by your rendering function. They are made up of textured polygons and the code to render them is generally fairly short (a few dozen lines). In the examples/ folder we provide functions for popular graphics API (OpenGL, DirectX, etc.).
- Each rendering function decides on a data type to represent "textures". The concept of what is a "texture" is entirely tied to your underlying engine/graphics API. We carry the information to identify a "texture" in the ImTextureID type. ImTextureID is nothing more that a void*, aka 4/8 bytes worth of data: just enough to store 1 pointer or 1 integer of your choice. Dear ImGui doesn't know or understand what you are storing in ImTextureID, it merely pass ImTextureID values until they reach your rendering function.
- In the examples/ bindings, for each graphics API binding we decided on a type that is likely to be a good representation for specifying an image from the end-user perspective. This is what the _examples_ rendering functions are using:
```
OpenGL: ImTextureID = GLuint (see ImGui_ImplOpenGL3_RenderDrawData() in imgui_impl_opengl3.cpp)
DirectX9: ImTextureID = LPDIRECT3DTEXTURE9 (see ImGui_ImplDX9_RenderDrawData() in imgui_impl_dx9.cpp)
DirectX11: ImTextureID = ID3D11ShaderResourceView* (see ImGui_ImplDX11_RenderDrawData() in imgui_impl_dx11.cpp)
DirectX12: ImTextureID = D3D12_GPU_DESCRIPTOR_HANDLE (see ImGui_ImplDX12_RenderDrawData() in imgui_impl_dx12.cpp)
```
For example, in the OpenGL example binding we store raw OpenGL texture identifier (GLuint) inside ImTextureID. Whereas in the DirectX11 example binding we store a pointer to ID3D11ShaderResourceView inside ImTextureID, which is a higher-level structure tying together both the texture and information about its format and how to read it.
- If you have a custom engine built over e.g. OpenGL, instead of passing GLuint around you may decide to use a high-level data type to carry information about the texture as well as how to display it (shaders, etc.). The decision of what to use as ImTextureID can always be made better knowing how your codebase is designed. If your engine has high-level data types for "textures" and "material" then you may want to use them. If you are starting with OpenGL or DirectX or Vulkan and haven't built much of a rendering engine over them, keeping the default ImTextureID representation suggested by the example bindings is probably the best choice. (Advanced users may also decide to keep a low-level type in ImTextureID, and use ImDrawList callback and pass information to their renderer)
User code may do:
```
// Cast our texture type to ImTextureID / void*
MyTexture* texture = g_CoffeeTableTexture;
ImGui::Image((void*)texture, ImVec2(texture->Width, texture->Height));
```
The renderer function called after ImGui::Render() will receive that same value that the user code passed:
```
// Cast ImTextureID / void* stored in the draw command as our texture type
MyTexture* texture = (MyTexture*)pcmd->TextureId;
MyEngineBindTexture2D(texture);
```
Once you understand this design you can begin understand that loading image files and turning them into displayable textures is not within the scope of Dear ImGui. This is by design and is actually a good thing, because it means your code has full control over your data types and how you display them. In reality, the concept of what constitute a "texture" is largely open-ended, and Dear ImGui doesn't want to narrow that concept. If you want to display an image file (e.g. PNG file) into the screen, please refer to tutorials above.
Finally, you may call `ImGui::ShowMetricsWindow()` to explore/visualize/understand how the ImDrawList are generated.
## About Texture Coordinates
See e.g. http://wiki.polycount.com/wiki/Texture_Coordinates
@ -521,4 +546,6 @@ ImGui::Image((void*)texture, ImVec2(texture->Width, texture->Height), ImVec2(0.0
ImGui::Image((void*)texture, ImVec2(texture->Width*0.5f, texture->Height*0.5f), ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f));
```
![Scaled](https://user-images.githubusercontent.com/8225057/79073856-25b40380-7ce9-11ea-91e4-754c3232fb58.png)
![Scaled](https://user-images.githubusercontent.com/8225057/79073856-25b40380-7ce9-11ea-91e4-754c3232fb58.png)
##### [Return to Index](#index)