This patch adds a Hydra render delegate to Cycles, allowing Cycles to be used for rendering in applications that provide a Hydra viewport. The implementation was written from scratch against Cycles X, for integration into the Blender repository to make it possible to continue developing it in step with the rest of Cycles. For this purpose it follows the style of the rest of the Cycles code and can be built with a CMake option (`WITH_CYCLES_HYDRA_RENDER_DELEGATE=1`) similar to the existing standalone version of Cycles. Since Hydra render delegates need to be built against the exact USD version and other dependencies as the target application is using, this is intended to be built separate from Blender (`WITH_BLENDER=0` CMake option) and with support for library versions different from what Blender is using. As such the CMake build scripts for Windows had to be modified slightly, so that the Cycles Hydra render delegate can e.g. be built with MSVC 2017 again even though Blender requires MSVC 2019 now, and it's possible to specify custom paths to the USD SDK etc. The codebase supports building against the latest USD release 22.03 and all the way back to USD 20.08 (with some limitations). Reviewed By: brecht, LazyDodo Differential Revision: https://developer.blender.org/D14398
241 lines
6.0 KiB
C++
241 lines
6.0 KiB
C++
/* SPDX-License-Identifier: Apache-2.0
|
|
* Copyright 2022 NVIDIA Corporation
|
|
* Copyright 2022 Blender Foundation */
|
|
|
|
#ifdef _WIN32
|
|
// Include first to avoid "NOGDI" definition set in Cycles headers
|
|
# include <Windows.h>
|
|
#endif
|
|
|
|
#include "hydra/display_driver.h"
|
|
#include "hydra/render_buffer.h"
|
|
#include "hydra/session.h"
|
|
|
|
#include <GL/glew.h>
|
|
#include <pxr/imaging/hgiGL/texture.h>
|
|
|
|
HDCYCLES_NAMESPACE_OPEN_SCOPE
|
|
|
|
HdCyclesDisplayDriver::HdCyclesDisplayDriver(HdCyclesSession *renderParam, Hgi *hgi)
|
|
: _renderParam(renderParam), _hgi(hgi)
|
|
{
|
|
#ifdef _WIN32
|
|
hdc_ = GetDC(CreateWindowA("STATIC",
|
|
"HdCycles",
|
|
WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
|
|
0,
|
|
0,
|
|
64,
|
|
64,
|
|
NULL,
|
|
NULL,
|
|
GetModuleHandle(NULL),
|
|
NULL));
|
|
|
|
int pixelFormat = GetPixelFormat(wglGetCurrentDC());
|
|
PIXELFORMATDESCRIPTOR pfd = {sizeof(pfd)};
|
|
DescribePixelFormat((HDC)hdc_, pixelFormat, sizeof(pfd), &pfd);
|
|
SetPixelFormat((HDC)hdc_, pixelFormat, &pfd);
|
|
|
|
TF_VERIFY(gl_context_ = wglCreateContext((HDC)hdc_));
|
|
TF_VERIFY(wglShareLists(wglGetCurrentContext(), (HGLRC)gl_context_));
|
|
#endif
|
|
|
|
glewInit();
|
|
|
|
glGenBuffers(1, &gl_pbo_id_);
|
|
}
|
|
|
|
HdCyclesDisplayDriver::~HdCyclesDisplayDriver()
|
|
{
|
|
if (texture_) {
|
|
_hgi->DestroyTexture(&texture_);
|
|
}
|
|
|
|
glDeleteBuffers(1, &gl_pbo_id_);
|
|
|
|
#ifdef _WIN32
|
|
TF_VERIFY(wglDeleteContext((HGLRC)gl_context_));
|
|
DestroyWindow(WindowFromDC((HDC)hdc_));
|
|
#endif
|
|
}
|
|
|
|
void HdCyclesDisplayDriver::next_tile_begin()
|
|
{
|
|
}
|
|
|
|
bool HdCyclesDisplayDriver::update_begin(const Params ¶ms,
|
|
int texture_width,
|
|
int texture_height)
|
|
{
|
|
#ifdef _WIN32
|
|
if (!hdc_ || !gl_context_) {
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
graphics_interop_activate();
|
|
|
|
if (gl_render_sync_) {
|
|
glWaitSync((GLsync)gl_render_sync_, 0, GL_TIMEOUT_IGNORED);
|
|
}
|
|
|
|
if (pbo_size_.x != params.full_size.x || pbo_size_.y != params.full_size.y) {
|
|
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, gl_pbo_id_);
|
|
glBufferData(GL_PIXEL_UNPACK_BUFFER,
|
|
sizeof(half4) * params.full_size.x * params.full_size.y,
|
|
0,
|
|
GL_DYNAMIC_DRAW);
|
|
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
|
|
|
pbo_size_ = params.full_size;
|
|
}
|
|
|
|
need_update_ = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
void HdCyclesDisplayDriver::update_end()
|
|
{
|
|
gl_upload_sync_ = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
|
|
|
glFlush();
|
|
|
|
graphics_interop_deactivate();
|
|
}
|
|
|
|
void HdCyclesDisplayDriver::flush()
|
|
{
|
|
graphics_interop_activate();
|
|
|
|
if (gl_upload_sync_) {
|
|
glWaitSync((GLsync)gl_upload_sync_, 0, GL_TIMEOUT_IGNORED);
|
|
}
|
|
|
|
if (gl_render_sync_) {
|
|
glWaitSync((GLsync)gl_render_sync_, 0, GL_TIMEOUT_IGNORED);
|
|
}
|
|
|
|
graphics_interop_deactivate();
|
|
}
|
|
|
|
half4 *HdCyclesDisplayDriver::map_texture_buffer()
|
|
{
|
|
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, gl_pbo_id_);
|
|
|
|
const auto mapped_rgba_pixels = static_cast<half4 *>(
|
|
glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY));
|
|
|
|
if (need_clear_ && mapped_rgba_pixels) {
|
|
memset(mapped_rgba_pixels, 0, sizeof(half4) * pbo_size_.x * pbo_size_.y);
|
|
need_clear_ = false;
|
|
}
|
|
|
|
return mapped_rgba_pixels;
|
|
}
|
|
|
|
void HdCyclesDisplayDriver::unmap_texture_buffer()
|
|
{
|
|
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
|
|
|
|
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
|
}
|
|
|
|
DisplayDriver::GraphicsInterop HdCyclesDisplayDriver::graphics_interop_get()
|
|
{
|
|
GraphicsInterop interop_dst;
|
|
interop_dst.buffer_width = pbo_size_.x;
|
|
interop_dst.buffer_height = pbo_size_.y;
|
|
interop_dst.opengl_pbo_id = gl_pbo_id_;
|
|
|
|
interop_dst.need_clear = need_clear_;
|
|
need_clear_ = false;
|
|
|
|
return interop_dst;
|
|
}
|
|
|
|
void HdCyclesDisplayDriver::graphics_interop_activate()
|
|
{
|
|
mutex_.lock();
|
|
|
|
#ifdef _WIN32
|
|
// Do not change context if this is called in the main thread
|
|
if (wglGetCurrentContext() == nullptr) {
|
|
TF_VERIFY(wglMakeCurrent((HDC)hdc_, (HGLRC)gl_context_));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void HdCyclesDisplayDriver::graphics_interop_deactivate()
|
|
{
|
|
#ifdef _WIN32
|
|
if (wglGetCurrentContext() == gl_context_) {
|
|
TF_VERIFY(wglMakeCurrent(nullptr, nullptr));
|
|
}
|
|
#endif
|
|
|
|
mutex_.unlock();
|
|
}
|
|
|
|
void HdCyclesDisplayDriver::clear()
|
|
{
|
|
need_clear_ = true;
|
|
}
|
|
|
|
void HdCyclesDisplayDriver::draw(const Params ¶ms)
|
|
{
|
|
const auto renderBuffer = static_cast<HdCyclesRenderBuffer *>(
|
|
_renderParam->GetDisplayAovBinding().renderBuffer);
|
|
if (!renderBuffer || // Ensure this render buffer matches the texture dimensions
|
|
(renderBuffer->GetWidth() != params.size.x || renderBuffer->GetHeight() != params.size.y)) {
|
|
return;
|
|
}
|
|
|
|
// Cycles 'DisplayDriver' only supports 'half4' format
|
|
TF_VERIFY(renderBuffer->GetFormat() == HdFormatFloat16Vec4);
|
|
|
|
const thread_scoped_lock lock(mutex_);
|
|
|
|
const GfVec3i dimensions(params.size.x, params.size.y, 1);
|
|
if (!texture_ || texture_->GetDescriptor().dimensions != dimensions) {
|
|
if (texture_) {
|
|
_hgi->DestroyTexture(&texture_);
|
|
}
|
|
|
|
HgiTextureDesc texDesc;
|
|
texDesc.usage = 0;
|
|
texDesc.format = HgiFormatFloat16Vec4;
|
|
texDesc.type = HgiTextureType2D;
|
|
texDesc.dimensions = dimensions;
|
|
texDesc.sampleCount = HgiSampleCount1;
|
|
|
|
texture_ = _hgi->CreateTexture(texDesc);
|
|
|
|
renderBuffer->SetResource(VtValue(texture_));
|
|
}
|
|
|
|
HgiGLTexture *const texture = dynamic_cast<HgiGLTexture *>(texture_.Get());
|
|
if (!texture || !need_update_ || pbo_size_.x != params.size.x || pbo_size_.y != params.size.y) {
|
|
return;
|
|
}
|
|
|
|
if (gl_upload_sync_) {
|
|
glWaitSync((GLsync)gl_upload_sync_, 0, GL_TIMEOUT_IGNORED);
|
|
}
|
|
|
|
glBindTexture(GL_TEXTURE_2D, texture->GetTextureId());
|
|
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, gl_pbo_id_);
|
|
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, pbo_size_.x, pbo_size_.y, GL_RGBA, GL_HALF_FLOAT, 0);
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
|
|
|
gl_render_sync_ = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
|
|
|
glFlush();
|
|
|
|
need_update_ = false;
|
|
}
|
|
|
|
HDCYCLES_NAMESPACE_CLOSE_SCOPE
|