XR: Color Depth Adjustments

This addresses reduced visibility of scenes (as displayed in the VR
headset) that can result from the 8-bit color depth format currently
used for XR swapchain images.

By switching to a swapchain format with higher color depth (RGB10_A2,
RGBA16, RGBA16F) for supported runtimes, visibility in VR should be
noticeably improved.

However, current limitations are lack of support for these higher
color depth formats by some XR runtimes, especially for OpenGL.

Also important to note that GPU_offscreen_create() now explicitly
takes in the texture format (eGPUTextureFormat) instead of a
"high_bitdepth" boolean.

Reviewed By: Julian Eisel, Clément Foucault

Differential Revision: http://developer.blender.org/D9842
This commit is contained in:
Peter Kim 2021-08-16 11:46:09 +09:00
parent 899935d5d0
commit eb278f5e12
17 changed files with 168 additions and 33 deletions

View File

@ -669,6 +669,14 @@ typedef struct {
void *exit_customdata;
} GHOST_XrSessionBeginInfo;
/** Texture format for XR swapchain. */
typedef enum GHOST_TXrSwapchainFormat {
GHOST_kXrSwapchainFormatRGBA8,
GHOST_kXrSwapchainFormatRGBA16,
GHOST_kXrSwapchainFormatRGBA16F,
GHOST_kXrSwapchainFormatRGB10_A2,
} GHOST_TXrSwapchainFormat;
typedef struct GHOST_XrDrawViewInfo {
int ofsx, ofsy;
int width, height;
@ -681,6 +689,7 @@ typedef struct GHOST_XrDrawViewInfo {
float angle_up, angle_down;
} fov;
GHOST_TXrSwapchainFormat swapchain_format;
/** Set if the buffer should be submitted with a SRGB transfer applied. */
char expects_srgb_buffer;

View File

@ -132,6 +132,7 @@ class GHOST_SharedOpenGLResource {
ID3D11DeviceContext *device_ctx,
unsigned int width,
unsigned int height,
DXGI_FORMAT format,
ID3D11RenderTargetView *render_target = nullptr)
: m_device(device), m_device_ctx(device_ctx), m_cur_width(width), m_cur_height(height)
{
@ -144,7 +145,7 @@ class GHOST_SharedOpenGLResource {
texDesc.Width = width;
texDesc.Height = height;
texDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
texDesc.Format = format;
texDesc.SampleDesc.Count = 1;
texDesc.ArraySize = 1;
texDesc.MipLevels = 1;
@ -321,7 +322,10 @@ class GHOST_SharedOpenGLResource {
};
GHOST_SharedOpenGLResource *GHOST_ContextD3D::createSharedOpenGLResource(
unsigned int width, unsigned int height, ID3D11RenderTargetView *render_target)
unsigned int width,
unsigned int height,
DXGI_FORMAT format,
ID3D11RenderTargetView *render_target)
{
if (!(WGL_NV_DX_interop && WGL_NV_DX_interop2)) {
fprintf(stderr,
@ -330,14 +334,15 @@ GHOST_SharedOpenGLResource *GHOST_ContextD3D::createSharedOpenGLResource(
return nullptr;
}
GHOST_SharedOpenGLResource *shared_res = new GHOST_SharedOpenGLResource(
m_device, m_device_ctx, width, height, render_target);
m_device, m_device_ctx, width, height, format, render_target);
return shared_res;
}
GHOST_SharedOpenGLResource *GHOST_ContextD3D::createSharedOpenGLResource(unsigned int width,
unsigned int height)
unsigned int height,
DXGI_FORMAT format)
{
return createSharedOpenGLResource(width, height, nullptr);
return createSharedOpenGLResource(width, height, format, nullptr);
}
void GHOST_ContextD3D::disposeSharedOpenGLResource(GHOST_SharedOpenGLResource *shared_res)

View File

@ -106,9 +106,13 @@ class GHOST_ContextD3D : public GHOST_Context {
}
class GHOST_SharedOpenGLResource *createSharedOpenGLResource(
unsigned int width, unsigned int height, ID3D11RenderTargetView *render_target);
unsigned int width,
unsigned int height,
DXGI_FORMAT format,
ID3D11RenderTargetView *render_target);
class GHOST_SharedOpenGLResource *createSharedOpenGLResource(unsigned int width,
unsigned int height);
unsigned int height,
DXGI_FORMAT format);
void disposeSharedOpenGLResource(class GHOST_SharedOpenGLResource *shared_res);
GHOST_TSuccess blitFromOpenGLContext(class GHOST_SharedOpenGLResource *shared_res,
unsigned int width,

View File

@ -60,6 +60,7 @@ class GHOST_IXrGraphicsBinding {
std::string *r_requirement_info) const = 0;
virtual void initFromGhostContext(class GHOST_Context &ghost_ctx) = 0;
virtual std::optional<int64_t> chooseSwapchainFormat(const std::vector<int64_t> &runtime_formats,
GHOST_TXrSwapchainFormat &r_format,
bool &r_is_rgb_format) const = 0;
virtual std::vector<XrSwapchainImageBaseHeader *> createSwapchainImages(
uint32_t image_count) = 0;

View File

@ -38,6 +38,7 @@
# include "GHOST_SystemWin32.h"
#endif
#include "GHOST_C-api.h"
#include "GHOST_XrException.h"
#include "GHOST_Xr_intern.h"
#include "GHOST_IXrGraphicsBinding.h"
@ -160,16 +161,41 @@ class GHOST_XrGraphicsBindingOpenGL : public GHOST_IXrGraphicsBinding {
}
std::optional<int64_t> chooseSwapchainFormat(const std::vector<int64_t> &runtime_formats,
GHOST_TXrSwapchainFormat &r_format,
bool &r_is_srgb_format) const override
{
std::vector<int64_t> gpu_binding_formats = {
GL_RGB10_A2,
GL_RGBA16,
GL_RGBA16F,
GL_RGBA8,
GL_SRGB8_ALPHA8,
};
std::optional result = choose_swapchain_format_from_candidates(gpu_binding_formats,
runtime_formats);
r_is_srgb_format = result ? (*result == GL_SRGB8_ALPHA8) : false;
if (result) {
switch (*result) {
case GL_RGB10_A2:
r_format = GHOST_kXrSwapchainFormatRGB10_A2;
break;
case GL_RGBA16:
r_format = GHOST_kXrSwapchainFormatRGBA16;
break;
case GL_RGBA16F:
r_format = GHOST_kXrSwapchainFormatRGBA16F;
break;
case GL_RGBA8:
case GL_SRGB8_ALPHA8:
r_format = GHOST_kXrSwapchainFormatRGBA8;
break;
}
r_is_srgb_format = (*result == GL_SRGB8_ALPHA8);
}
else {
r_format = GHOST_kXrSwapchainFormatRGBA8;
r_is_srgb_format = false;
}
return result;
}
@ -228,6 +254,33 @@ class GHOST_XrGraphicsBindingOpenGL : public GHOST_IXrGraphicsBinding {
};
#ifdef WIN32
static void ghost_format_to_dx_format(GHOST_TXrSwapchainFormat ghost_format,
bool expects_srgb_buffer,
DXGI_FORMAT &r_dx_format)
{
r_dx_format = DXGI_FORMAT_UNKNOWN;
switch (ghost_format) {
case GHOST_kXrSwapchainFormatRGBA8:
r_dx_format = expects_srgb_buffer ? DXGI_FORMAT_R8G8B8A8_UNORM_SRGB :
DXGI_FORMAT_R8G8B8A8_UNORM;
break;
case GHOST_kXrSwapchainFormatRGBA16:
r_dx_format = DXGI_FORMAT_R16G16B16A16_UNORM;
break;
case GHOST_kXrSwapchainFormatRGBA16F:
r_dx_format = DXGI_FORMAT_R16G16B16A16_FLOAT;
break;
case GHOST_kXrSwapchainFormatRGB10_A2:
r_dx_format = DXGI_FORMAT_R10G10B10A2_UNORM;
break;
}
if (r_dx_format == DXGI_FORMAT_UNKNOWN) {
throw GHOST_XrException("No supported DirectX swapchain format found.");
}
}
class GHOST_XrGraphicsBindingD3D : public GHOST_IXrGraphicsBinding {
public:
GHOST_XrGraphicsBindingD3D(GHOST_Context &ghost_ctx)
@ -284,16 +337,48 @@ class GHOST_XrGraphicsBindingD3D : public GHOST_IXrGraphicsBinding {
}
std::optional<int64_t> chooseSwapchainFormat(const std::vector<int64_t> &runtime_formats,
GHOST_TXrSwapchainFormat &r_format,
bool &r_is_srgb_format) const override
{
std::vector<int64_t> gpu_binding_formats = {
DXGI_FORMAT_R8G8B8A8_UNORM,
DXGI_FORMAT_R8G8B8A8_UNORM_SRGB,
# if 0 /* RGB10A2 doesn't seem to work with Oculus headsets, so move it after RGB16AF for the \
time being. */
DXGI_FORMAT_R10G10B10A2_UNORM,
# endif
DXGI_FORMAT_R16G16B16A16_UNORM,
DXGI_FORMAT_R16G16B16A16_FLOAT,
# if 1
DXGI_FORMAT_R10G10B10A2_UNORM,
# endif
DXGI_FORMAT_R8G8B8A8_UNORM,
DXGI_FORMAT_R8G8B8A8_UNORM_SRGB,
};
std::optional result = choose_swapchain_format_from_candidates(gpu_binding_formats,
runtime_formats);
r_is_srgb_format = result ? (*result == DXGI_FORMAT_R8G8B8A8_UNORM_SRGB) : false;
if (result) {
switch (*result) {
case DXGI_FORMAT_R10G10B10A2_UNORM:
r_format = GHOST_kXrSwapchainFormatRGB10_A2;
break;
case DXGI_FORMAT_R16G16B16A16_UNORM:
r_format = GHOST_kXrSwapchainFormatRGBA16;
break;
case DXGI_FORMAT_R16G16B16A16_FLOAT:
r_format = GHOST_kXrSwapchainFormatRGBA16F;
break;
case DXGI_FORMAT_R8G8B8A8_UNORM:
case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
r_format = GHOST_kXrSwapchainFormatRGBA8;
break;
}
r_is_srgb_format = (*result == DXGI_FORMAT_R8G8B8A8_UNORM_SRGB);
}
else {
r_format = GHOST_kXrSwapchainFormatRGBA8;
r_is_srgb_format = false;
}
return result;
}
@ -334,14 +419,18 @@ class GHOST_XrGraphicsBindingD3D : public GHOST_IXrGraphicsBinding {
m_ghost_ctx->m_device->CreateRenderTargetView(d3d_swapchain_image.texture, &rtv_desc, &rtv);
if (!m_shared_resource) {
DXGI_FORMAT format;
ghost_format_to_dx_format(draw_info.swapchain_format, draw_info.expects_srgb_buffer, format);
m_shared_resource = m_ghost_ctx->createSharedOpenGLResource(
draw_info.width, draw_info.height, rtv);
draw_info.width, draw_info.height, format, rtv);
}
m_ghost_ctx->blitFromOpenGLContext(m_shared_resource, draw_info.width, draw_info.height);
# else
if (!m_shared_resource) {
m_shared_resource = m_ghost_d3d_ctx->createSharedOpenGLResource(draw_info.width,
draw_info.height);
DXGI_FORMAT format;
ghost_format_to_dx_format(draw_info.swapchain_format, draw_info.expects_srgb_buffer, format);
m_shared_resource = m_ghost_d3d_ctx->createSharedOpenGLResource(
draw_info.width, draw_info.height, format);
}
m_ghost_d3d_ctx->blitFromOpenGLContext(m_shared_resource, draw_info.width, draw_info.height);

View File

@ -422,6 +422,7 @@ void GHOST_XrSession::drawView(GHOST_XrSwapchain &swapchain,
assert(view_idx < 256);
draw_view_info.view_idx = (char)view_idx;
draw_view_info.swapchain_format = swapchain.getFormat();
draw_view_info.expects_srgb_buffer = swapchain.isBufferSRGB();
draw_view_info.ofsx = r_proj_layer_view.subImage.imageRect.offset.x;
draw_view_info.ofsy = r_proj_layer_view.subImage.imageRect.offset.y;

View File

@ -67,8 +67,8 @@ GHOST_XrSwapchain::GHOST_XrSwapchain(GHOST_IXrGraphicsBinding &gpu_binding,
"Failed to get swapchain image formats.");
assert(swapchain_formats.size() == format_count);
std::optional chosen_format = gpu_binding.chooseSwapchainFormat(swapchain_formats,
m_is_srgb_buffer);
std::optional chosen_format = gpu_binding.chooseSwapchainFormat(
swapchain_formats, m_format, m_is_srgb_buffer);
if (!chosen_format) {
throw GHOST_XrException(
"Error: No format matching OpenXR runtime supported swapchain formats found.");
@ -97,6 +97,7 @@ GHOST_XrSwapchain::GHOST_XrSwapchain(GHOST_XrSwapchain &&other)
: m_oxr(std::move(other.m_oxr)),
m_image_width(other.m_image_width),
m_image_height(other.m_image_height),
m_format(other.m_format),
m_is_srgb_buffer(other.m_is_srgb_buffer)
{
/* Prevent xrDestroySwapchain call for the moved out item. */
@ -134,7 +135,12 @@ void GHOST_XrSwapchain::updateCompositionLayerProjectViewSubImage(XrSwapchainSub
r_sub_image.imageRect.extent = {m_image_width, m_image_height};
}
bool GHOST_XrSwapchain::isBufferSRGB()
GHOST_TXrSwapchainFormat GHOST_XrSwapchain::getFormat() const
{
return m_format;
}
bool GHOST_XrSwapchain::isBufferSRGB() const
{
return m_is_srgb_buffer;
}

View File

@ -37,10 +37,12 @@ class GHOST_XrSwapchain {
void updateCompositionLayerProjectViewSubImage(XrSwapchainSubImage &r_sub_image);
bool isBufferSRGB();
GHOST_TXrSwapchainFormat getFormat() const;
bool isBufferSRGB() const;
private:
std::unique_ptr<OpenXRSwapchainData> m_oxr; /* Could use stack, but PImpl is preferable. */
int32_t m_image_width, m_image_height;
GHOST_TXrSwapchainFormat m_format;
bool m_is_srgb_buffer = false;
};

View File

@ -646,7 +646,8 @@ static bool gpencil_render_offscreen(tGPDfill *tgpf)
tgpf->sizey = (int)tgpf->region->winy;
char err_out[256] = "unknown";
GPUOffScreen *offscreen = GPU_offscreen_create(tgpf->sizex, tgpf->sizey, true, false, err_out);
GPUOffScreen *offscreen = GPU_offscreen_create(
tgpf->sizex, tgpf->sizey, true, GPU_RGBA8, err_out);
if (offscreen == NULL) {
printf("GPencil - Fill - Unable to create fill buffer\n");
return false;

View File

@ -768,7 +768,7 @@ static bool screen_opengl_render_init(bContext *C, wmOperator *op)
/* corrects render size with actual size, not every card supports non-power-of-two dimensions */
DRW_opengl_context_enable(); /* Off-screen creation needs to be done in DRW context. */
ofs = GPU_offscreen_create(sizex, sizey, true, true, err_out);
ofs = GPU_offscreen_create(sizex, sizey, true, GPU_RGBA16F, err_out);
DRW_opengl_context_disable();
if (!ofs) {

View File

@ -451,7 +451,7 @@ static void screen_preview_draw(const bScreen *screen, int size_x, int size_y)
void ED_screen_preview_render(const bScreen *screen, int size_x, int size_y, uint *r_rect)
{
char err_out[256] = "unknown";
GPUOffScreen *offscreen = GPU_offscreen_create(size_x, size_y, true, false, err_out);
GPUOffScreen *offscreen = GPU_offscreen_create(size_x, size_y, true, GPU_RGBA8, err_out);
GPU_offscreen_bind(offscreen, true);
GPU_clear_color(0.0f, 0.0f, 0.0f, 0.0f);

View File

@ -1876,7 +1876,7 @@ ImBuf *ED_view3d_draw_offscreen_imbuf(Depsgraph *depsgraph,
if (own_ofs) {
/* bind */
ofs = GPU_offscreen_create(sizex, sizey, true, false, err_out);
ofs = GPU_offscreen_create(sizex, sizey, true, GPU_RGBA8, err_out);
if (ofs == NULL) {
DRW_opengl_context_disable();
return NULL;

View File

@ -223,7 +223,7 @@ uint GPU_framebuffer_stack_level_get(void);
*/
GPUOffScreen *GPU_offscreen_create(
int width, int height, bool depth, bool high_bitdepth, char err_out[256]);
int width, int height, bool depth, eGPUTextureFormat format, char err_out[256]);
void GPU_offscreen_free(GPUOffScreen *ofs);
void GPU_offscreen_bind(GPUOffScreen *ofs, bool save);
void GPU_offscreen_unbind(GPUOffScreen *ofs, bool restore);

View File

@ -591,7 +591,7 @@ static GPUFrameBuffer *gpu_offscreen_fb_get(GPUOffScreen *ofs)
}
GPUOffScreen *GPU_offscreen_create(
int width, int height, bool depth, bool high_bitdepth, char err_out[256])
int width, int height, bool depth, eGPUTextureFormat format, char err_out[256])
{
GPUOffScreen *ofs = (GPUOffScreen *)MEM_callocN(sizeof(GPUOffScreen), __func__);
@ -600,8 +600,7 @@ GPUOffScreen *GPU_offscreen_create(
height = max_ii(1, height);
width = max_ii(1, width);
ofs->color = GPU_texture_create_2d(
"ofs_color", width, height, 1, (high_bitdepth) ? GPU_RGBA16F : GPU_RGBA8, nullptr);
ofs->color = GPU_texture_create_2d("ofs_color", width, height, 1, format, nullptr);
if (depth) {
ofs->depth = GPU_texture_create_2d(

View File

@ -227,7 +227,7 @@ static PyObject *pygpu_offscreen__tp_new(PyTypeObject *UNUSED(self),
}
if (GPU_context_active_get()) {
ofs = GPU_offscreen_create(width, height, true, false, err_out);
ofs = GPU_offscreen_create(width, height, true, GPU_RGBA8, err_out);
}
else {
STRNCPY(err_out, "No active GPU context found");

View File

@ -455,7 +455,7 @@ static void wm_draw_region_buffer_create(ARegion *region, bool stereo, bool use_
* depth or multisample buffers. 3D view creates own buffers with
* the data it needs. */
GPUOffScreen *offscreen = GPU_offscreen_create(
region->winx, region->winy, false, false, NULL);
region->winx, region->winy, false, GPU_RGBA8, NULL);
if (!offscreen) {
WM_report(RPT_ERROR, "Region could not be drawn!");
return;
@ -888,7 +888,7 @@ static void wm_draw_window(bContext *C, wmWindow *win)
* stereo methods, but it's less efficient than drawing directly. */
const int width = WM_window_pixels_x(win);
const int height = WM_window_pixels_y(win);
GPUOffScreen *offscreen = GPU_offscreen_create(width, height, false, false, NULL);
GPUOffScreen *offscreen = GPU_offscreen_create(width, height, false, GPU_RGBA8, NULL);
if (offscreen) {
GPUTexture *texture = GPU_offscreen_color_texture(offscreen);

View File

@ -657,9 +657,6 @@ bool wm_xr_session_surface_offscreen_ensure(wmXrSurfaceData *surface_data,
GPUViewport *viewport = vp->viewport;
const bool size_changed = offscreen && (GPU_offscreen_width(offscreen) != draw_view->width) &&
(GPU_offscreen_height(offscreen) != draw_view->height);
char err_out[256] = "unknown";
bool failure = false;
if (offscreen) {
BLI_assert(viewport);
@ -670,8 +667,29 @@ bool wm_xr_session_surface_offscreen_ensure(wmXrSurfaceData *surface_data,
GPU_offscreen_free(offscreen);
}
char err_out[256] = "unknown";
bool failure = false;
eGPUTextureFormat format =
GPU_R8; /* Initialize with some unsupported format to check following switch statement. */
switch (draw_view->swapchain_format) {
case GHOST_kXrSwapchainFormatRGBA8:
format = GPU_RGBA8;
break;
case GHOST_kXrSwapchainFormatRGBA16:
format = GPU_RGBA16;
break;
case GHOST_kXrSwapchainFormatRGBA16F:
format = GPU_RGBA16F;
break;
case GHOST_kXrSwapchainFormatRGB10_A2:
format = GPU_RGB10_A2;
break;
}
BLI_assert(format != GPU_R8);
offscreen = vp->offscreen = GPU_offscreen_create(
draw_view->width, draw_view->height, true, false, err_out);
draw_view->width, draw_view->height, true, format, err_out);
if (offscreen) {
viewport = vp->viewport = GPU_viewport_create();
if (!viewport) {