Compare commits
305 Commits
temp-node-
...
xr-control
Author | SHA1 | Date | |
---|---|---|---|
6fc81d6bca | |||
85e1f28fca | |||
c4f65fa5da | |||
d3afe0c126 | |||
e863e05697 | |||
b57b4dfab1 | |||
f49dff97d4 | |||
74f45ed9c5 | |||
![]() |
c5c94e3eae | ||
34cf33eb12 | |||
![]() |
54927caf4f | ||
12e8c78353 | |||
1476d35870 | |||
1fb364491b | |||
eacdc0ab4a | |||
dc30a9087c | |||
9e456ca695 | |||
aae96176e8 | |||
e1952c541a | |||
![]() |
fb820496f5 | ||
f497e471f8 | |||
3be4cb5b27 | |||
56ce51d1f7 | |||
1c7ce7e0b4 | |||
271210126e | |||
f9acf21063 | |||
bdc66c9569 | |||
eb3a8fb4e8 | |||
798e593002 | |||
21c29480c3 | |||
928d644895 | |||
af0b7925db | |||
7843cd63d8 | |||
ae4b45145c | |||
2e6c6426d3 | |||
bdb7d262aa | |||
b559fb178e | |||
4485dc483c | |||
![]() |
3a59ddb292 | ||
66fe1c79f3 | |||
e2df5c8a56 | |||
f3274bfa70 | |||
4569d9c0c3 | |||
![]() |
33dc584b37 | ||
![]() |
213554f24a | ||
8d60ac2bb0 | |||
![]() |
1a134c4c30 | ||
a754e35198 | |||
ac582056e2 | |||
![]() |
be70827e6f | ||
827e30bd15 | |||
6cff1d6480 | |||
dd3391dd99 | |||
4389067929 | |||
4ee2d9df42 | |||
42ce88f15c | |||
628fab696c | |||
5d42ea0369 | |||
d754d85845 | |||
1a72744ddc | |||
779ea49af7 | |||
07c5d02a11 | |||
dda9762a16 | |||
fa23369373 | |||
1ef02d8f4b | |||
6d7113c363 | |||
33306067da | |||
ff5733ef2f | |||
c996926c05 | |||
593621fdfb | |||
07a92c616f | |||
99ce2f3e4d | |||
c8c782fbf4 | |||
0f1ef110a4 | |||
5fd158bb87 | |||
21a24aa61e | |||
c9a4c29589 | |||
a063d58080 | |||
f58bebf688 | |||
ba79625f9e | |||
72381da800 | |||
9d68c34a75 | |||
e41cc8162a | |||
1990bb921f | |||
567c22f0d8 | |||
be0a272d91 | |||
5f5289512c | |||
05c3e5c433 | |||
9f6b8bc3a1 | |||
d870b85dab | |||
2f4712841a | |||
6dec69daa8 | |||
be7653fe52 | |||
c600251eae | |||
d2c4094425 | |||
14fc1c73e8 | |||
ef07af330a | |||
9561a0a0a3 | |||
9f5089b67a | |||
f34cb9f8bf | |||
5a640b94b4 | |||
9025f2adc6 | |||
d5989308fb | |||
8a103470c9 | |||
be59b7699e | |||
6b67760bd8 | |||
fb3154447b | |||
c56b73277e | |||
99beac7b3f | |||
c4c4b1a03d | |||
af90b8aaa3 | |||
2c5241ad01 | |||
6daab062b4 | |||
4b3aaa76bd | |||
707bc260d8 | |||
5b45070024 | |||
bd8b3f57df | |||
4040ebd1ff | |||
3feb3a4707 | |||
aa92c23430 | |||
7863f2fbe0 | |||
872484dfaa | |||
1185259db8 | |||
1f1589b4df | |||
6ee46d5e83 | |||
a177eb3dd2 | |||
f7d42065eb | |||
7a7ee11f13 | |||
5465112930 | |||
f89f63eefd | |||
714224ee1f | |||
1184da2974 | |||
e8926d40c8 | |||
004034f8c5 | |||
7b867dd00a | |||
e022f99e5c | |||
80c80f5e18 | |||
0d6c6a6787 | |||
e3cab5c206 | |||
8ace7897a8 | |||
e5089d3924 | |||
65804b203e | |||
2a768ffe43 | |||
2585d7f1b8 | |||
eed31613e5 | |||
6732d31f18 | |||
1bd403430d | |||
9926553255 | |||
0c92bf5e37 | |||
9ce38d17ed | |||
40741a8942 | |||
eeb948f428 | |||
9c3ac44c89 | |||
819a7e7900 | |||
5544ffabd5 | |||
2a3b8880e3 | |||
05f5d2791f | |||
29702c6d8b | |||
36463a16bb | |||
b57cc27ec9 | |||
8b53855371 | |||
af1c2869a6 | |||
9df2fae994 | |||
daf5570c42 | |||
795ae6433d | |||
1e8b3692b9 | |||
544c6fd1b6 | |||
963a6c3c12 | |||
f799fc3033 | |||
5a786038cb | |||
7343845d7b | |||
ce5fc090a8 | |||
2fa8e2686e | |||
f89460a872 | |||
8c8bc114b8 | |||
11a63417ea | |||
ed869a2609 | |||
79ed8f21f7 | |||
a5d1f3e4c5 | |||
7c05339597 | |||
cd3030bb8d | |||
5677c02954 | |||
3e7524b52b | |||
c34d0fbee2 | |||
63f0dcf6b7 | |||
517a3cdad1 | |||
585f98784c | |||
c9c052aae0 | |||
9cfc8ebcc9 | |||
dfc8860502 | |||
97c8878753 | |||
49707f5d83 | |||
334114d287 | |||
034dd0d702 | |||
d8cf5e7f4d | |||
854d115d68 | |||
886cba7fe8 | |||
f21eb52ecc | |||
81944920a7 | |||
cf12206311 | |||
247267dee4 | |||
2370781389 | |||
1aecb31acc | |||
2092d39dfd | |||
412ea63063 | |||
0f236af817 | |||
798c33ab3b | |||
a87b142e51 | |||
2ddf55f358 | |||
84f821f271 | |||
29ed6a6872 | |||
78563e9bf1 | |||
8916a04df8 | |||
c9f0da5b20 | |||
f7a72a238b | |||
c8db91fc93 | |||
e089ad2b4b | |||
6c67d808b7 | |||
d3e352a7ec | |||
4423ab787b | |||
57f7e1ff97 | |||
f8ad9c696e | |||
79733505bf | |||
b4a21355d7 | |||
d685b9ca57 | |||
d3b07d5ad5 | |||
86faaaa934 | |||
4d5f104faf | |||
6fdf493028 | |||
43dd6ba330 | |||
423619fd3d | |||
6489002982 | |||
634812e579 | |||
8ccdd43fc1 | |||
78d7dbbad9 | |||
4ec85983af | |||
dc0b81bc99 | |||
f6e71179fe | |||
36bdbe6ce3 | |||
5f7285d4af | |||
1625bb3e71 | |||
a399445b3c | |||
f32d320e90 | |||
78cd4e8cee | |||
848c87c6d9 | |||
f5ff515085 | |||
07a7c88918 | |||
48ec546911 | |||
8335da48f9 | |||
8fae3debf3 | |||
2ed0965e97 | |||
2b338373d7 | |||
c565bb8985 | |||
03bb02337f | |||
618df6b0b7 | |||
0c246b2b52 | |||
ff2dcdb447 | |||
73daf1f721 | |||
9c28e6e62f | |||
9c288d3e20 | |||
86f76f46f9 | |||
498804edc8 | |||
3d24ef827c | |||
c8be04b7f8 | |||
58ecc0d1bf | |||
1a6d76c5c7 | |||
a1333cf2c3 | |||
9be27de831 | |||
a90bfa17a9 | |||
d72fafd62a | |||
62c5f71da1 | |||
09377b4c9c | |||
b596d70a1f | |||
1d86e1ff46 | |||
3e0acdfe94 | |||
a6c5a1a2dc | |||
ceab20d5e5 | |||
cb75a83df0 | |||
db1cdcdafe | |||
297decbe1f | |||
8af91a18e0 | |||
9ec8ca2d55 | |||
5561fbf1f8 | |||
de026d1395 | |||
07e5488c61 | |||
2eb44677dd | |||
3e33c89828 | |||
f2385a3f05 | |||
f6a7d5336b | |||
336c8a628a | |||
66e22a2ed7 | |||
6920906f75 | |||
ae1d8f4c35 | |||
e0eafd2757 | |||
5e8d3f6d28 | |||
090c1b7dda | |||
f126ce2d5b | |||
3b3955d9e2 | |||
9c9081bf83 | |||
cfc5c1b46d | |||
45194962c1 | |||
921510f34f | |||
d7083de46d | |||
b44173754f | |||
e0da725075 |
@@ -5,7 +5,7 @@
|
||||
update-code:
|
||||
git:
|
||||
submodules:
|
||||
- branch: master
|
||||
- branch: xr-controller-support
|
||||
commit_id: HEAD
|
||||
path: release/scripts/addons
|
||||
- branch: master
|
||||
|
@@ -178,7 +178,7 @@ def submodules_update(args, release_version, branch):
|
||||
branch = branch_fallback
|
||||
|
||||
submodules = [
|
||||
("release/scripts/addons", branch, branch_fallback),
|
||||
("release/scripts/addons", "xr-controller-support", branch_fallback),
|
||||
("release/scripts/addons_contrib", branch, branch_fallback),
|
||||
("release/datafiles/locale", branch, branch_fallback),
|
||||
("source/tools", branch, branch_fallback),
|
||||
|
@@ -0,0 +1,40 @@
|
||||
"""
|
||||
This method enables conversions between Local and Pose space for bones in
|
||||
the middle of updating the armature without having to update dependencies
|
||||
after each change, by manually carrying updated matrices in a recursive walk.
|
||||
"""
|
||||
|
||||
def set_pose_matrices(obj, matrix_map):
|
||||
"Assign pose space matrices of all bones at once, ignoring constraints."
|
||||
|
||||
def rec(pbone, parent_matrix):
|
||||
matrix = matrix_map[pbone.name]
|
||||
|
||||
## Instead of:
|
||||
# pbone.matrix = matrix
|
||||
# bpy.context.view_layer.update()
|
||||
|
||||
# Compute and assign local matrix, using the new parent matrix
|
||||
if pbone.parent:
|
||||
pbone.matrix_basis = pbone.bone.convert_local_to_pose(
|
||||
matrix,
|
||||
pbone.bone.matrix_local,
|
||||
parent_matrix=parent_matrix,
|
||||
parent_matrix_local=pbone.parent.bone.matrix_local,
|
||||
invert=True
|
||||
)
|
||||
else:
|
||||
pbone.matrix_basis = pbone.bone.convert_local_to_pose(
|
||||
matrix,
|
||||
pbone.bone.matrix_local,
|
||||
invert=True
|
||||
)
|
||||
|
||||
# Recursively process children, passing the new matrix through
|
||||
for child in pbone.children:
|
||||
rec(child, matrix)
|
||||
|
||||
# Scan all bone trees from their roots
|
||||
for pbone in obj.pose.bones:
|
||||
if not pbone.parent:
|
||||
rec(pbone, None)
|
@@ -64,6 +64,8 @@ if(WITH_CYCLES_STANDALONE)
|
||||
cycles_standalone.cpp
|
||||
cycles_xml.cpp
|
||||
cycles_xml.h
|
||||
oiio_output_driver.cpp
|
||||
oiio_output_driver.h
|
||||
)
|
||||
add_executable(cycles ${SRC} ${INC} ${INC_SYS})
|
||||
unset(SRC)
|
||||
@@ -73,7 +75,7 @@ if(WITH_CYCLES_STANDALONE)
|
||||
|
||||
if(APPLE)
|
||||
if(WITH_OPENCOLORIO)
|
||||
set_property(TARGET cycles APPEND_STRING PROPERTY LINK_FLAGS " -framework IOKit")
|
||||
set_property(TARGET cycles APPEND_STRING PROPERTY LINK_FLAGS " -framework IOKit -framework Carbon")
|
||||
endif()
|
||||
if(WITH_OPENIMAGEDENOISE AND "${CMAKE_OSX_ARCHITECTURES}" STREQUAL "arm64")
|
||||
# OpenImageDenoise uses BNNS from the Accelerate framework.
|
||||
|
@@ -36,6 +36,9 @@
|
||||
#include "util/util_unique_ptr.h"
|
||||
#include "util/util_version.h"
|
||||
|
||||
#include "app/cycles_xml.h"
|
||||
#include "app/oiio_output_driver.h"
|
||||
|
||||
#ifdef WITH_CYCLES_STANDALONE_GUI
|
||||
# include "util/util_view.h"
|
||||
#endif
|
||||
@@ -54,6 +57,7 @@ struct Options {
|
||||
bool quiet;
|
||||
bool show_help, interactive, pause;
|
||||
string output_filepath;
|
||||
string output_pass;
|
||||
} options;
|
||||
|
||||
static void session_print(const string &str)
|
||||
@@ -89,30 +93,6 @@ static void session_print_status()
|
||||
session_print(status);
|
||||
}
|
||||
|
||||
static bool write_render(const uchar *pixels, int w, int h, int channels)
|
||||
{
|
||||
string msg = string_printf("Writing image %s", options.output_path.c_str());
|
||||
session_print(msg);
|
||||
|
||||
unique_ptr<ImageOutput> out = unique_ptr<ImageOutput>(ImageOutput::create(options.output_path));
|
||||
if (!out) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ImageSpec spec(w, h, channels, TypeDesc::UINT8);
|
||||
if (!out->open(options.output_path, spec)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* conversion for different top/bottom convention */
|
||||
out->write_image(
|
||||
TypeDesc::UINT8, pixels + (h - 1) * w * channels, AutoStride, -w * channels, AutoStride);
|
||||
|
||||
out->close();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static BufferParams &session_buffer_params()
|
||||
{
|
||||
static BufferParams buffer_params;
|
||||
@@ -147,9 +127,14 @@ static void scene_init()
|
||||
|
||||
static void session_init()
|
||||
{
|
||||
options.session_params.write_render_cb = write_render;
|
||||
options.output_pass = "combined";
|
||||
options.session = new Session(options.session_params, options.scene_params);
|
||||
|
||||
if (!options.output_filepath.empty()) {
|
||||
options.session->set_output_driver(make_unique<OIIOOutputDriver>(
|
||||
options.output_filepath, options.output_pass, session_print));
|
||||
}
|
||||
|
||||
if (options.session_params.background && !options.quiet)
|
||||
options.session->progress.set_update_callback(function_bind(&session_print_status));
|
||||
#ifdef WITH_CYCLES_STANDALONE_GUI
|
||||
@@ -160,6 +145,11 @@ static void session_init()
|
||||
/* load scene */
|
||||
scene_init();
|
||||
|
||||
/* add pass for output. */
|
||||
Pass *pass = options.scene->create_node<Pass>();
|
||||
pass->set_name(ustring(options.output_pass.c_str()));
|
||||
pass->set_type(PASS_COMBINED);
|
||||
|
||||
options.session->reset(options.session_params, session_buffer_params());
|
||||
options.session->start();
|
||||
}
|
||||
|
@@ -333,6 +333,7 @@ static void xml_read_shader_graph(XMLReadState &state, Shader *shader, xml_node
|
||||
}
|
||||
|
||||
snode = (ShaderNode *)node_type->create(node_type);
|
||||
snode->set_owner(graph);
|
||||
}
|
||||
|
||||
xml_read_node(graph_reader, snode, node);
|
||||
|
71
intern/cycles/app/oiio_output_driver.cpp
Normal file
71
intern/cycles/app/oiio_output_driver.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2021 Blender Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "app/oiio_output_driver.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
OIIOOutputDriver::OIIOOutputDriver(const string_view filepath,
|
||||
const string_view pass,
|
||||
LogFunction log)
|
||||
: filepath_(filepath), pass_(pass), log_(log)
|
||||
{
|
||||
}
|
||||
|
||||
OIIOOutputDriver::~OIIOOutputDriver()
|
||||
{
|
||||
}
|
||||
|
||||
void OIIOOutputDriver::write_render_tile(const Tile &tile)
|
||||
{
|
||||
/* Only write the full buffer, no intermediate tiles. */
|
||||
if (!(tile.size == tile.full_size)) {
|
||||
return;
|
||||
}
|
||||
|
||||
log_(string_printf("Writing image %s", filepath_.c_str()));
|
||||
|
||||
unique_ptr<ImageOutput> image_output(ImageOutput::create(filepath_));
|
||||
if (image_output == nullptr) {
|
||||
log_("Failed to create image file");
|
||||
return;
|
||||
}
|
||||
|
||||
const int width = tile.size.x;
|
||||
const int height = tile.size.y;
|
||||
|
||||
ImageSpec spec(width, height, 4, TypeDesc::FLOAT);
|
||||
if (!image_output->open(filepath_, spec)) {
|
||||
log_("Failed to create image file");
|
||||
return;
|
||||
}
|
||||
|
||||
vector<float> pixels(width * height * 4);
|
||||
if (!tile.get_pass_pixels(pass_, 4, pixels.data())) {
|
||||
log_("Failed to read render pass pixels");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Manipulate offset and stride to convert from bottom-up to top-down convention. */
|
||||
image_output->write_image(TypeDesc::FLOAT,
|
||||
pixels.data() + (height - 1) * width * 4,
|
||||
AutoStride,
|
||||
-width * 4 * sizeof(float),
|
||||
AutoStride);
|
||||
image_output->close();
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
42
intern/cycles/app/oiio_output_driver.h
Normal file
42
intern/cycles/app/oiio_output_driver.h
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2021 Blender Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "render/output_driver.h"
|
||||
|
||||
#include "util/util_function.h"
|
||||
#include "util/util_image.h"
|
||||
#include "util/util_string.h"
|
||||
#include "util/util_unique_ptr.h"
|
||||
#include "util/util_vector.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
class OIIOOutputDriver : public OutputDriver {
|
||||
public:
|
||||
typedef function<void(const string &)> LogFunction;
|
||||
|
||||
OIIOOutputDriver(const string_view filepath, const string_view pass, LogFunction log);
|
||||
virtual ~OIIOOutputDriver();
|
||||
|
||||
void write_render_tile(const Tile &tile) override;
|
||||
|
||||
protected:
|
||||
string filepath_;
|
||||
string pass_;
|
||||
LogFunction log_;
|
||||
};
|
||||
|
||||
CCL_NAMESPACE_END
|
@@ -31,13 +31,14 @@ set(INC_SYS
|
||||
set(SRC
|
||||
blender_camera.cpp
|
||||
blender_device.cpp
|
||||
blender_display_driver.cpp
|
||||
blender_image.cpp
|
||||
blender_geometry.cpp
|
||||
blender_gpu_display.cpp
|
||||
blender_light.cpp
|
||||
blender_mesh.cpp
|
||||
blender_object.cpp
|
||||
blender_object_cull.cpp
|
||||
blender_output_driver.cpp
|
||||
blender_particles.cpp
|
||||
blender_curves.cpp
|
||||
blender_logging.cpp
|
||||
@@ -51,10 +52,11 @@ set(SRC
|
||||
|
||||
CCL_api.h
|
||||
blender_device.h
|
||||
blender_gpu_display.h
|
||||
blender_display_driver.h
|
||||
blender_id_map.h
|
||||
blender_image.h
|
||||
blender_object_cull.h
|
||||
blender_output_driver.h
|
||||
blender_sync.h
|
||||
blender_session.h
|
||||
blender_texture.h
|
||||
|
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "blender/blender_gpu_display.h"
|
||||
#include "blender/blender_display_driver.h"
|
||||
|
||||
#include "device/device.h"
|
||||
#include "util/util_logging.h"
|
||||
@@ -273,17 +273,17 @@ uint BlenderDisplaySpaceShader::get_shader_program()
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* BlenderGPUDisplay.
|
||||
* BlenderDisplayDriver.
|
||||
*/
|
||||
|
||||
BlenderGPUDisplay::BlenderGPUDisplay(BL::RenderEngine &b_engine, BL::Scene &b_scene)
|
||||
BlenderDisplayDriver::BlenderDisplayDriver(BL::RenderEngine &b_engine, BL::Scene &b_scene)
|
||||
: b_engine_(b_engine), display_shader_(BlenderDisplayShader::create(b_engine, b_scene))
|
||||
{
|
||||
/* Create context while on the main thread. */
|
||||
gl_context_create();
|
||||
}
|
||||
|
||||
BlenderGPUDisplay::~BlenderGPUDisplay()
|
||||
BlenderDisplayDriver::~BlenderDisplayDriver()
|
||||
{
|
||||
gl_resources_destroy();
|
||||
}
|
||||
@@ -292,19 +292,18 @@ BlenderGPUDisplay::~BlenderGPUDisplay()
|
||||
* Update procedure.
|
||||
*/
|
||||
|
||||
bool BlenderGPUDisplay::do_update_begin(const GPUDisplayParams ¶ms,
|
||||
bool BlenderDisplayDriver::update_begin(const Params ¶ms,
|
||||
int texture_width,
|
||||
int texture_height)
|
||||
{
|
||||
/* Note that it's the responsibility of BlenderGPUDisplay to ensure updating and drawing
|
||||
/* Note that it's the responsibility of BlenderDisplayDriver to ensure updating and drawing
|
||||
* the texture does not happen at the same time. This is achieved indirectly.
|
||||
*
|
||||
* When enabling the OpenGL context, it uses an internal mutex lock DST.gl_context_lock.
|
||||
* This same lock is also held when do_draw() is called, which together ensure mutual
|
||||
* exclusion.
|
||||
*
|
||||
* This locking is not performed at the GPU display level, because that would cause lock
|
||||
* inversion. */
|
||||
* This locking is not performed on the Cycles side, because that would cause lock inversion. */
|
||||
if (!gl_context_enable()) {
|
||||
return false;
|
||||
}
|
||||
@@ -361,7 +360,7 @@ bool BlenderGPUDisplay::do_update_begin(const GPUDisplayParams ¶ms,
|
||||
return true;
|
||||
}
|
||||
|
||||
void BlenderGPUDisplay::do_update_end()
|
||||
void BlenderDisplayDriver::update_end()
|
||||
{
|
||||
gl_upload_sync_ = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
glFlush();
|
||||
@@ -369,54 +368,18 @@ void BlenderGPUDisplay::do_update_end()
|
||||
gl_context_disable();
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* Texture update from CPU buffer.
|
||||
*/
|
||||
|
||||
void BlenderGPUDisplay::do_copy_pixels_to_texture(
|
||||
const half4 *rgba_pixels, int texture_x, int texture_y, int pixels_width, int pixels_height)
|
||||
{
|
||||
/* This call copies pixels to a Pixel Buffer Object (PBO) which is much cheaper from CPU time
|
||||
* point of view than to copy data directly to the OpenGL texture.
|
||||
*
|
||||
* The possible downside of this approach is that it might require a higher peak memory when
|
||||
* doing partial updates of the texture (although, in practice even partial updates might peak
|
||||
* with a full-frame buffer stored on the CPU if the GPU is currently occupied). */
|
||||
|
||||
half4 *mapped_rgba_pixels = map_texture_buffer();
|
||||
if (!mapped_rgba_pixels) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (texture_x == 0 && texture_y == 0 && pixels_width == texture_.width &&
|
||||
pixels_height == texture_.height) {
|
||||
const size_t size_in_bytes = sizeof(half4) * texture_.width * texture_.height;
|
||||
memcpy(mapped_rgba_pixels, rgba_pixels, size_in_bytes);
|
||||
}
|
||||
else {
|
||||
const half4 *rgba_row = rgba_pixels;
|
||||
half4 *mapped_rgba_row = mapped_rgba_pixels + texture_y * texture_.width + texture_x;
|
||||
for (int y = 0; y < pixels_height;
|
||||
++y, rgba_row += pixels_width, mapped_rgba_row += texture_.width) {
|
||||
memcpy(mapped_rgba_row, rgba_row, sizeof(half4) * pixels_width);
|
||||
}
|
||||
}
|
||||
|
||||
unmap_texture_buffer();
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* Texture buffer mapping.
|
||||
*/
|
||||
|
||||
half4 *BlenderGPUDisplay::do_map_texture_buffer()
|
||||
half4 *BlenderDisplayDriver::map_texture_buffer()
|
||||
{
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture_.gl_pbo_id);
|
||||
|
||||
half4 *mapped_rgba_pixels = reinterpret_cast<half4 *>(
|
||||
glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY));
|
||||
if (!mapped_rgba_pixels) {
|
||||
LOG(ERROR) << "Error mapping BlenderGPUDisplay pixel buffer object.";
|
||||
LOG(ERROR) << "Error mapping BlenderDisplayDriver pixel buffer object.";
|
||||
}
|
||||
|
||||
if (texture_.need_clear) {
|
||||
@@ -431,7 +394,7 @@ half4 *BlenderGPUDisplay::do_map_texture_buffer()
|
||||
return mapped_rgba_pixels;
|
||||
}
|
||||
|
||||
void BlenderGPUDisplay::do_unmap_texture_buffer()
|
||||
void BlenderDisplayDriver::unmap_texture_buffer()
|
||||
{
|
||||
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
|
||||
|
||||
@@ -442,9 +405,9 @@ void BlenderGPUDisplay::do_unmap_texture_buffer()
|
||||
* Graphics interoperability.
|
||||
*/
|
||||
|
||||
DeviceGraphicsInteropDestination BlenderGPUDisplay::do_graphics_interop_get()
|
||||
BlenderDisplayDriver::GraphicsInterop BlenderDisplayDriver::graphics_interop_get()
|
||||
{
|
||||
DeviceGraphicsInteropDestination interop_dst;
|
||||
GraphicsInterop interop_dst;
|
||||
|
||||
interop_dst.buffer_width = texture_.buffer_width;
|
||||
interop_dst.buffer_height = texture_.buffer_height;
|
||||
@@ -456,12 +419,12 @@ DeviceGraphicsInteropDestination BlenderGPUDisplay::do_graphics_interop_get()
|
||||
return interop_dst;
|
||||
}
|
||||
|
||||
void BlenderGPUDisplay::graphics_interop_activate()
|
||||
void BlenderDisplayDriver::graphics_interop_activate()
|
||||
{
|
||||
gl_context_enable();
|
||||
}
|
||||
|
||||
void BlenderGPUDisplay::graphics_interop_deactivate()
|
||||
void BlenderDisplayDriver::graphics_interop_deactivate()
|
||||
{
|
||||
gl_context_disable();
|
||||
}
|
||||
@@ -470,17 +433,17 @@ void BlenderGPUDisplay::graphics_interop_deactivate()
|
||||
* Drawing.
|
||||
*/
|
||||
|
||||
void BlenderGPUDisplay::clear()
|
||||
void BlenderDisplayDriver::clear()
|
||||
{
|
||||
texture_.need_clear = true;
|
||||
}
|
||||
|
||||
void BlenderGPUDisplay::set_zoom(float zoom_x, float zoom_y)
|
||||
void BlenderDisplayDriver::set_zoom(float zoom_x, float zoom_y)
|
||||
{
|
||||
zoom_ = make_float2(zoom_x, zoom_y);
|
||||
}
|
||||
|
||||
void BlenderGPUDisplay::do_draw(const GPUDisplayParams ¶ms)
|
||||
void BlenderDisplayDriver::draw(const Params ¶ms)
|
||||
{
|
||||
/* See do_update_begin() for why no locking is required here. */
|
||||
const bool transparent = true; // TODO(sergey): Derive this from Film.
|
||||
@@ -497,7 +460,7 @@ void BlenderGPUDisplay::do_draw(const GPUDisplayParams ¶ms)
|
||||
/* Texture is requested to be cleared and was not yet cleared.
|
||||
*
|
||||
* Do early return which should be equivalent of drawing all-zero texture.
|
||||
* Watchout for the lock though so that the clear happening during update is properly
|
||||
* Watch out for the lock though so that the clear happening during update is properly
|
||||
* synchronized here. */
|
||||
gl_context_mutex_.unlock();
|
||||
return;
|
||||
@@ -584,7 +547,7 @@ void BlenderGPUDisplay::do_draw(const GPUDisplayParams ¶ms)
|
||||
}
|
||||
}
|
||||
|
||||
void BlenderGPUDisplay::gl_context_create()
|
||||
void BlenderDisplayDriver::gl_context_create()
|
||||
{
|
||||
/* When rendering in viewport there is no render context available via engine.
|
||||
* Check whether own context is to be created here.
|
||||
@@ -613,7 +576,7 @@ void BlenderGPUDisplay::gl_context_create()
|
||||
}
|
||||
}
|
||||
|
||||
bool BlenderGPUDisplay::gl_context_enable()
|
||||
bool BlenderDisplayDriver::gl_context_enable()
|
||||
{
|
||||
if (use_gl_context_) {
|
||||
if (!gl_context_) {
|
||||
@@ -628,7 +591,7 @@ bool BlenderGPUDisplay::gl_context_enable()
|
||||
return true;
|
||||
}
|
||||
|
||||
void BlenderGPUDisplay::gl_context_disable()
|
||||
void BlenderDisplayDriver::gl_context_disable()
|
||||
{
|
||||
if (use_gl_context_) {
|
||||
if (gl_context_) {
|
||||
@@ -641,7 +604,7 @@ void BlenderGPUDisplay::gl_context_disable()
|
||||
RE_engine_render_context_disable(reinterpret_cast<RenderEngine *>(b_engine_.ptr.data));
|
||||
}
|
||||
|
||||
void BlenderGPUDisplay::gl_context_dispose()
|
||||
void BlenderDisplayDriver::gl_context_dispose()
|
||||
{
|
||||
if (gl_context_) {
|
||||
const bool drw_state = DRW_opengl_context_release();
|
||||
@@ -653,7 +616,7 @@ void BlenderGPUDisplay::gl_context_dispose()
|
||||
}
|
||||
}
|
||||
|
||||
bool BlenderGPUDisplay::gl_draw_resources_ensure()
|
||||
bool BlenderDisplayDriver::gl_draw_resources_ensure()
|
||||
{
|
||||
if (!texture_.gl_id) {
|
||||
/* If there is no texture allocated, there is nothing to draw. Inform the draw call that it can
|
||||
@@ -680,7 +643,7 @@ bool BlenderGPUDisplay::gl_draw_resources_ensure()
|
||||
return true;
|
||||
}
|
||||
|
||||
void BlenderGPUDisplay::gl_resources_destroy()
|
||||
void BlenderDisplayDriver::gl_resources_destroy()
|
||||
{
|
||||
gl_context_enable();
|
||||
|
||||
@@ -703,7 +666,7 @@ void BlenderGPUDisplay::gl_resources_destroy()
|
||||
gl_context_dispose();
|
||||
}
|
||||
|
||||
bool BlenderGPUDisplay::gl_texture_resources_ensure()
|
||||
bool BlenderDisplayDriver::gl_texture_resources_ensure()
|
||||
{
|
||||
if (texture_.creation_attempted) {
|
||||
return texture_.is_created;
|
||||
@@ -740,7 +703,7 @@ bool BlenderGPUDisplay::gl_texture_resources_ensure()
|
||||
return true;
|
||||
}
|
||||
|
||||
void BlenderGPUDisplay::texture_update_if_needed()
|
||||
void BlenderDisplayDriver::texture_update_if_needed()
|
||||
{
|
||||
if (!texture_.need_update) {
|
||||
return;
|
||||
@@ -754,7 +717,7 @@ void BlenderGPUDisplay::texture_update_if_needed()
|
||||
texture_.need_update = false;
|
||||
}
|
||||
|
||||
void BlenderGPUDisplay::vertex_buffer_update(const GPUDisplayParams ¶ms)
|
||||
void BlenderDisplayDriver::vertex_buffer_update(const Params ¶ms)
|
||||
{
|
||||
/* Invalidate old contents - avoids stalling if the buffer is still waiting in queue to be
|
||||
* rendered. */
|
||||
@@ -767,23 +730,23 @@ void BlenderGPUDisplay::vertex_buffer_update(const GPUDisplayParams ¶ms)
|
||||
|
||||
vpointer[0] = 0.0f;
|
||||
vpointer[1] = 0.0f;
|
||||
vpointer[2] = params.offset.x;
|
||||
vpointer[3] = params.offset.y;
|
||||
vpointer[2] = params.full_offset.x;
|
||||
vpointer[3] = params.full_offset.y;
|
||||
|
||||
vpointer[4] = 1.0f;
|
||||
vpointer[5] = 0.0f;
|
||||
vpointer[6] = (float)params.size.x + params.offset.x;
|
||||
vpointer[7] = params.offset.y;
|
||||
vpointer[6] = (float)params.size.x + params.full_offset.x;
|
||||
vpointer[7] = params.full_offset.y;
|
||||
|
||||
vpointer[8] = 1.0f;
|
||||
vpointer[9] = 1.0f;
|
||||
vpointer[10] = (float)params.size.x + params.offset.x;
|
||||
vpointer[11] = (float)params.size.y + params.offset.y;
|
||||
vpointer[10] = (float)params.size.x + params.full_offset.x;
|
||||
vpointer[11] = (float)params.size.y + params.full_offset.y;
|
||||
|
||||
vpointer[12] = 0.0f;
|
||||
vpointer[13] = 1.0f;
|
||||
vpointer[14] = params.offset.x;
|
||||
vpointer[15] = (float)params.size.y + params.offset.y;
|
||||
vpointer[14] = params.full_offset.x;
|
||||
vpointer[15] = (float)params.size.y + params.full_offset.y;
|
||||
|
||||
glUnmapBuffer(GL_ARRAY_BUFFER);
|
||||
}
|
@@ -22,12 +22,14 @@
|
||||
|
||||
#include "RNA_blender_cpp.h"
|
||||
|
||||
#include "render/gpu_display.h"
|
||||
#include "render/display_driver.h"
|
||||
|
||||
#include "util/util_thread.h"
|
||||
#include "util/util_unique_ptr.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
/* Base class of shader used for GPU display rendering. */
|
||||
/* Base class of shader used for display driver rendering. */
|
||||
class BlenderDisplayShader {
|
||||
public:
|
||||
static constexpr const char *position_attribute_name = "pos";
|
||||
@@ -96,11 +98,11 @@ class BlenderDisplaySpaceShader : public BlenderDisplayShader {
|
||||
uint shader_program_ = 0;
|
||||
};
|
||||
|
||||
/* GPU display implementation which is specific for Blender viewport integration. */
|
||||
class BlenderGPUDisplay : public GPUDisplay {
|
||||
/* Display driver implementation which is specific for Blender viewport integration. */
|
||||
class BlenderDisplayDriver : public DisplayDriver {
|
||||
public:
|
||||
BlenderGPUDisplay(BL::RenderEngine &b_engine, BL::Scene &b_scene);
|
||||
~BlenderGPUDisplay();
|
||||
BlenderDisplayDriver(BL::RenderEngine &b_engine, BL::Scene &b_scene);
|
||||
~BlenderDisplayDriver();
|
||||
|
||||
virtual void graphics_interop_activate() override;
|
||||
virtual void graphics_interop_deactivate() override;
|
||||
@@ -110,22 +112,15 @@ class BlenderGPUDisplay : public GPUDisplay {
|
||||
void set_zoom(float zoom_x, float zoom_y);
|
||||
|
||||
protected:
|
||||
virtual bool do_update_begin(const GPUDisplayParams ¶ms,
|
||||
int texture_width,
|
||||
int texture_height) override;
|
||||
virtual void do_update_end() override;
|
||||
virtual bool update_begin(const Params ¶ms, int texture_width, int texture_height) override;
|
||||
virtual void update_end() override;
|
||||
|
||||
virtual void do_copy_pixels_to_texture(const half4 *rgba_pixels,
|
||||
int texture_x,
|
||||
int texture_y,
|
||||
int pixels_width,
|
||||
int pixels_height) override;
|
||||
virtual void do_draw(const GPUDisplayParams ¶ms) override;
|
||||
virtual half4 *map_texture_buffer() override;
|
||||
virtual void unmap_texture_buffer() override;
|
||||
|
||||
virtual half4 *do_map_texture_buffer() override;
|
||||
virtual void do_unmap_texture_buffer() override;
|
||||
virtual GraphicsInterop graphics_interop_get() override;
|
||||
|
||||
virtual DeviceGraphicsInteropDestination do_graphics_interop_get() override;
|
||||
virtual void draw(const Params ¶ms) override;
|
||||
|
||||
/* Helper function which allocates new GPU context. */
|
||||
void gl_context_create();
|
||||
@@ -152,13 +147,13 @@ class BlenderGPUDisplay : public GPUDisplay {
|
||||
* This buffer is used to render texture in the viewport.
|
||||
*
|
||||
* NOTE: The buffer needs to be bound. */
|
||||
void vertex_buffer_update(const GPUDisplayParams ¶ms);
|
||||
void vertex_buffer_update(const Params ¶ms);
|
||||
|
||||
BL::RenderEngine b_engine_;
|
||||
|
||||
/* OpenGL context which is used the render engine doesn't have its own. */
|
||||
void *gl_context_ = nullptr;
|
||||
/* The when Blender RenderEngine side context is not available and the GPUDisplay is to create
|
||||
/* The when Blender RenderEngine side context is not available and the DisplayDriver is to create
|
||||
* its own context. */
|
||||
bool use_gl_context_ = false;
|
||||
/* Mutex used to guard the `gl_context_`. */
|
127
intern/cycles/blender/blender_output_driver.cpp
Normal file
127
intern/cycles/blender/blender_output_driver.cpp
Normal file
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright 2021 Blender Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "blender/blender_output_driver.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
BlenderOutputDriver::BlenderOutputDriver(BL::RenderEngine &b_engine) : b_engine_(b_engine)
|
||||
{
|
||||
}
|
||||
|
||||
BlenderOutputDriver::~BlenderOutputDriver()
|
||||
{
|
||||
}
|
||||
|
||||
bool BlenderOutputDriver::read_render_tile(const Tile &tile)
|
||||
{
|
||||
/* Get render result. */
|
||||
BL::RenderResult b_rr = b_engine_.begin_result(tile.offset.x,
|
||||
tile.offset.y,
|
||||
tile.size.x,
|
||||
tile.size.y,
|
||||
tile.layer.c_str(),
|
||||
tile.view.c_str());
|
||||
|
||||
/* Can happen if the intersected rectangle gives 0 width or height. */
|
||||
if (b_rr.ptr.data == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
BL::RenderResult::layers_iterator b_single_rlay;
|
||||
b_rr.layers.begin(b_single_rlay);
|
||||
|
||||
/* layer will be missing if it was disabled in the UI */
|
||||
if (b_single_rlay == b_rr.layers.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
BL::RenderLayer b_rlay = *b_single_rlay;
|
||||
|
||||
vector<float> pixels(tile.size.x * tile.size.y * 4);
|
||||
|
||||
/* Copy each pass.
|
||||
* TODO:copy only the required ones for better performance? */
|
||||
for (BL::RenderPass &b_pass : b_rlay.passes) {
|
||||
tile.set_pass_pixels(b_pass.name(), b_pass.channels(), (float *)b_pass.rect());
|
||||
}
|
||||
|
||||
b_engine_.end_result(b_rr, false, false, false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BlenderOutputDriver::update_render_tile(const Tile &tile)
|
||||
{
|
||||
/* Use final write for preview renders, otherwise render result wouldn't be be updated
|
||||
* quickly on Blender side. For all other cases we use the display driver. */
|
||||
if (b_engine_.is_preview()) {
|
||||
write_render_tile(tile);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
/* Don't highlight full-frame tile. */
|
||||
if (!(tile.size == tile.full_size)) {
|
||||
b_engine_.tile_highlight_clear_all();
|
||||
b_engine_.tile_highlight_set(tile.offset.x, tile.offset.y, tile.size.x, tile.size.y, true);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void BlenderOutputDriver::write_render_tile(const Tile &tile)
|
||||
{
|
||||
b_engine_.tile_highlight_clear_all();
|
||||
|
||||
/* Get render result. */
|
||||
BL::RenderResult b_rr = b_engine_.begin_result(tile.offset.x,
|
||||
tile.offset.y,
|
||||
tile.size.x,
|
||||
tile.size.y,
|
||||
tile.layer.c_str(),
|
||||
tile.view.c_str());
|
||||
|
||||
/* Can happen if the intersected rectangle gives 0 width or height. */
|
||||
if (b_rr.ptr.data == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
BL::RenderResult::layers_iterator b_single_rlay;
|
||||
b_rr.layers.begin(b_single_rlay);
|
||||
|
||||
/* Layer will be missing if it was disabled in the UI. */
|
||||
if (b_single_rlay == b_rr.layers.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
BL::RenderLayer b_rlay = *b_single_rlay;
|
||||
|
||||
vector<float> pixels(tile.size.x * tile.size.y * 4);
|
||||
|
||||
/* Copy each pass. */
|
||||
for (BL::RenderPass &b_pass : b_rlay.passes) {
|
||||
if (!tile.get_pass_pixels(b_pass.name(), b_pass.channels(), &pixels[0])) {
|
||||
memset(&pixels[0], 0, pixels.size() * sizeof(float));
|
||||
}
|
||||
|
||||
b_pass.rect(&pixels[0]);
|
||||
}
|
||||
|
||||
b_engine_.end_result(b_rr, true, false, true);
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
40
intern/cycles/blender/blender_output_driver.h
Normal file
40
intern/cycles/blender/blender_output_driver.h
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2021 Blender Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "RNA_blender_cpp.h"
|
||||
|
||||
#include "render/output_driver.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
class BlenderOutputDriver : public OutputDriver {
|
||||
public:
|
||||
BlenderOutputDriver(BL::RenderEngine &b_engine);
|
||||
~BlenderOutputDriver();
|
||||
|
||||
virtual void write_render_tile(const Tile &tile) override;
|
||||
virtual bool update_render_tile(const Tile &tile) override;
|
||||
virtual bool read_render_tile(const Tile &tile) override;
|
||||
|
||||
protected:
|
||||
BL::RenderEngine b_engine_;
|
||||
};
|
||||
|
||||
CCL_NAMESPACE_END
|
@@ -42,7 +42,8 @@
|
||||
#include "util/util_progress.h"
|
||||
#include "util/util_time.h"
|
||||
|
||||
#include "blender/blender_gpu_display.h"
|
||||
#include "blender/blender_display_driver.h"
|
||||
#include "blender/blender_output_driver.h"
|
||||
#include "blender/blender_session.h"
|
||||
#include "blender/blender_sync.h"
|
||||
#include "blender/blender_util.h"
|
||||
@@ -157,11 +158,13 @@ void BlenderSession::create_session()
|
||||
b_v3d, b_rv3d, scene->camera, width, height);
|
||||
session->reset(session_params, buffer_params);
|
||||
|
||||
/* Create GPU display. */
|
||||
/* Create GPU display.
|
||||
* TODO(sergey): Investigate whether DisplayDriver can be used for the preview as well. */
|
||||
if (!b_engine.is_preview() && !headless) {
|
||||
unique_ptr<BlenderGPUDisplay> gpu_display = make_unique<BlenderGPUDisplay>(b_engine, b_scene);
|
||||
gpu_display_ = gpu_display.get();
|
||||
session->set_gpu_display(move(gpu_display));
|
||||
unique_ptr<BlenderDisplayDriver> display_driver = make_unique<BlenderDisplayDriver>(b_engine,
|
||||
b_scene);
|
||||
display_driver_ = display_driver.get();
|
||||
session->set_display_driver(move(display_driver));
|
||||
}
|
||||
|
||||
/* Viewport and preview (as in, material preview) does not do tiled rendering, so can inform
|
||||
@@ -278,96 +281,6 @@ void BlenderSession::free_session()
|
||||
session = nullptr;
|
||||
}
|
||||
|
||||
void BlenderSession::read_render_tile()
|
||||
{
|
||||
const int2 tile_offset = session->get_render_tile_offset();
|
||||
const int2 tile_size = session->get_render_tile_size();
|
||||
|
||||
/* get render result */
|
||||
BL::RenderResult b_rr = b_engine.begin_result(tile_offset.x,
|
||||
tile_offset.y,
|
||||
tile_size.x,
|
||||
tile_size.y,
|
||||
b_rlay_name.c_str(),
|
||||
b_rview_name.c_str());
|
||||
|
||||
/* can happen if the intersected rectangle gives 0 width or height */
|
||||
if (b_rr.ptr.data == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
BL::RenderResult::layers_iterator b_single_rlay;
|
||||
b_rr.layers.begin(b_single_rlay);
|
||||
|
||||
/* layer will be missing if it was disabled in the UI */
|
||||
if (b_single_rlay == b_rr.layers.end())
|
||||
return;
|
||||
|
||||
BL::RenderLayer b_rlay = *b_single_rlay;
|
||||
|
||||
vector<float> pixels(tile_size.x * tile_size.y * 4);
|
||||
|
||||
/* Copy each pass.
|
||||
* TODO:copy only the required ones for better performance? */
|
||||
for (BL::RenderPass &b_pass : b_rlay.passes) {
|
||||
session->set_render_tile_pixels(b_pass.name(), b_pass.channels(), (float *)b_pass.rect());
|
||||
}
|
||||
|
||||
b_engine.end_result(b_rr, false, false, false);
|
||||
}
|
||||
|
||||
void BlenderSession::write_render_tile()
|
||||
{
|
||||
const int2 tile_offset = session->get_render_tile_offset();
|
||||
const int2 tile_size = session->get_render_tile_size();
|
||||
|
||||
const string_view render_layer_name = session->get_render_tile_layer();
|
||||
const string_view render_view_name = session->get_render_tile_view();
|
||||
|
||||
b_engine.tile_highlight_clear_all();
|
||||
|
||||
/* get render result */
|
||||
BL::RenderResult b_rr = b_engine.begin_result(tile_offset.x,
|
||||
tile_offset.y,
|
||||
tile_size.x,
|
||||
tile_size.y,
|
||||
render_layer_name.c_str(),
|
||||
render_view_name.c_str());
|
||||
|
||||
/* can happen if the intersected rectangle gives 0 width or height */
|
||||
if (b_rr.ptr.data == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
BL::RenderResult::layers_iterator b_single_rlay;
|
||||
b_rr.layers.begin(b_single_rlay);
|
||||
|
||||
/* layer will be missing if it was disabled in the UI */
|
||||
if (b_single_rlay == b_rr.layers.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
BL::RenderLayer b_rlay = *b_single_rlay;
|
||||
|
||||
write_render_result(b_rlay);
|
||||
|
||||
b_engine.end_result(b_rr, true, false, true);
|
||||
}
|
||||
|
||||
void BlenderSession::update_render_tile()
|
||||
{
|
||||
if (!session->has_multiple_render_tiles()) {
|
||||
/* Don't highlight full-frame tile. */
|
||||
return;
|
||||
}
|
||||
|
||||
const int2 tile_offset = session->get_render_tile_offset();
|
||||
const int2 tile_size = session->get_render_tile_size();
|
||||
|
||||
b_engine.tile_highlight_clear_all();
|
||||
b_engine.tile_highlight_set(tile_offset.x, tile_offset.y, tile_size.x, tile_size.y, true);
|
||||
}
|
||||
|
||||
void BlenderSession::full_buffer_written(string_view filename)
|
||||
{
|
||||
full_buffer_files_.emplace_back(filename);
|
||||
@@ -441,18 +354,8 @@ void BlenderSession::render(BL::Depsgraph &b_depsgraph_)
|
||||
return;
|
||||
}
|
||||
|
||||
/* set callback to write out render results */
|
||||
session->write_render_tile_cb = [&]() { write_render_tile(); };
|
||||
|
||||
/* Use final write for preview renders, otherwise render result wouldn't be be updated on Blender
|
||||
* side. */
|
||||
/* TODO(sergey): Investigate whether GPUDisplay can be used for the preview as well. */
|
||||
if (b_engine.is_preview()) {
|
||||
session->update_render_tile_cb = [&]() { write_render_tile(); };
|
||||
}
|
||||
else {
|
||||
session->update_render_tile_cb = [&]() { update_render_tile(); };
|
||||
}
|
||||
/* Create driver to write out render results. */
|
||||
session->set_output_driver(make_unique<BlenderOutputDriver>(b_engine));
|
||||
|
||||
session->full_buffer_written_cb = [&](string_view filename) { full_buffer_written(filename); };
|
||||
|
||||
@@ -598,9 +501,8 @@ void BlenderSession::render_frame_finish()
|
||||
path_remove(filename);
|
||||
}
|
||||
|
||||
/* clear callback */
|
||||
session->write_render_tile_cb = function_null;
|
||||
session->update_render_tile_cb = function_null;
|
||||
/* Clear driver. */
|
||||
session->set_output_driver(nullptr);
|
||||
session->full_buffer_written_cb = function_null;
|
||||
}
|
||||
|
||||
@@ -706,9 +608,8 @@ void BlenderSession::bake(BL::Depsgraph &b_depsgraph_,
|
||||
pass->set_type(bake_type_to_pass(bake_type, bake_filter));
|
||||
pass->set_include_albedo((bake_filter & BL::BakeSettings::pass_filter_COLOR));
|
||||
|
||||
session->read_render_tile_cb = [&]() { read_render_tile(); };
|
||||
session->write_render_tile_cb = [&]() { write_render_tile(); };
|
||||
session->set_gpu_display(nullptr);
|
||||
session->set_display_driver(nullptr);
|
||||
session->set_output_driver(make_unique<BlenderOutputDriver>(b_engine));
|
||||
|
||||
if (!session->progress.get_cancel()) {
|
||||
/* Sync scene. */
|
||||
@@ -751,43 +652,7 @@ void BlenderSession::bake(BL::Depsgraph &b_depsgraph_,
|
||||
session->wait();
|
||||
}
|
||||
|
||||
session->read_render_tile_cb = function_null;
|
||||
session->write_render_tile_cb = function_null;
|
||||
}
|
||||
|
||||
void BlenderSession::write_render_result(BL::RenderLayer &b_rlay)
|
||||
{
|
||||
if (!session->copy_render_tile_from_device()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int2 tile_size = session->get_render_tile_size();
|
||||
vector<float> pixels(tile_size.x * tile_size.y * 4);
|
||||
|
||||
/* Copy each pass. */
|
||||
for (BL::RenderPass &b_pass : b_rlay.passes) {
|
||||
if (!session->get_render_tile_pixels(b_pass.name(), b_pass.channels(), &pixels[0])) {
|
||||
memset(&pixels[0], 0, pixels.size() * sizeof(float));
|
||||
}
|
||||
|
||||
b_pass.rect(&pixels[0]);
|
||||
}
|
||||
}
|
||||
|
||||
void BlenderSession::update_render_result(BL::RenderLayer &b_rlay)
|
||||
{
|
||||
if (!session->copy_render_tile_from_device()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int2 tile_size = session->get_render_tile_size();
|
||||
vector<float> pixels(tile_size.x * tile_size.y * 4);
|
||||
|
||||
/* Copy combined pass. */
|
||||
BL::RenderPass b_combined_pass(b_rlay.passes.find_by_name("Combined", b_rview_name.c_str()));
|
||||
if (session->get_render_tile_pixels("Combined", b_combined_pass.channels(), &pixels[0])) {
|
||||
b_combined_pass.rect(&pixels[0]);
|
||||
}
|
||||
session->set_output_driver(nullptr);
|
||||
}
|
||||
|
||||
void BlenderSession::synchronize(BL::Depsgraph &b_depsgraph_)
|
||||
@@ -895,7 +760,7 @@ void BlenderSession::draw(BL::SpaceImageEditor &space_image)
|
||||
}
|
||||
|
||||
BL::Array<float, 2> zoom = space_image.zoom();
|
||||
gpu_display_->set_zoom(zoom[0], zoom[1]);
|
||||
display_driver_->set_zoom(zoom[0], zoom[1]);
|
||||
|
||||
session->draw();
|
||||
}
|
||||
|
@@ -29,7 +29,7 @@
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
class BlenderGPUDisplay;
|
||||
class BlenderDisplayDriver;
|
||||
class BlenderSync;
|
||||
class ImageMetaData;
|
||||
class Scene;
|
||||
@@ -70,20 +70,7 @@ class BlenderSession {
|
||||
const int bake_width,
|
||||
const int bake_height);
|
||||
|
||||
void write_render_result(BL::RenderLayer &b_rlay);
|
||||
void write_render_tile();
|
||||
|
||||
void update_render_tile();
|
||||
|
||||
void full_buffer_written(string_view filename);
|
||||
|
||||
/* update functions are used to update display buffer only after sample was rendered
|
||||
* only needed for better visual feedback */
|
||||
void update_render_result(BL::RenderLayer &b_rlay);
|
||||
|
||||
/* read functions for baking input */
|
||||
void read_render_tile();
|
||||
|
||||
/* interactive updates */
|
||||
void synchronize(BL::Depsgraph &b_depsgraph);
|
||||
|
||||
@@ -164,8 +151,8 @@ class BlenderSession {
|
||||
int last_pass_index = -1;
|
||||
} draw_state_;
|
||||
|
||||
/* NOTE: The BlenderSession references the GPU display. */
|
||||
BlenderGPUDisplay *gpu_display_ = nullptr;
|
||||
/* NOTE: The BlenderSession references the display driver. */
|
||||
BlenderDisplayDriver *display_driver_ = nullptr;
|
||||
|
||||
vector<string> full_buffer_files_;
|
||||
};
|
||||
|
@@ -279,7 +279,7 @@ static ShaderNode *add_node(Scene *scene,
|
||||
array<float3> curve_mapping_curves;
|
||||
float min_x, max_x;
|
||||
curvemapping_color_to_array(mapping, curve_mapping_curves, RAMP_TABLE_SIZE, true);
|
||||
curvemapping_minmax(mapping, true, &min_x, &max_x);
|
||||
curvemapping_minmax(mapping, 4, &min_x, &max_x);
|
||||
curves->set_min_x(min_x);
|
||||
curves->set_max_x(max_x);
|
||||
curves->set_curves(curve_mapping_curves);
|
||||
@@ -292,12 +292,25 @@ static ShaderNode *add_node(Scene *scene,
|
||||
array<float3> curve_mapping_curves;
|
||||
float min_x, max_x;
|
||||
curvemapping_color_to_array(mapping, curve_mapping_curves, RAMP_TABLE_SIZE, false);
|
||||
curvemapping_minmax(mapping, false, &min_x, &max_x);
|
||||
curvemapping_minmax(mapping, 3, &min_x, &max_x);
|
||||
curves->set_min_x(min_x);
|
||||
curves->set_max_x(max_x);
|
||||
curves->set_curves(curve_mapping_curves);
|
||||
node = curves;
|
||||
}
|
||||
else if (b_node.is_a(&RNA_ShaderNodeFloatCurve)) {
|
||||
BL::ShaderNodeFloatCurve b_curve_node(b_node);
|
||||
BL::CurveMapping mapping(b_curve_node.mapping());
|
||||
FloatCurveNode *curve = graph->create_node<FloatCurveNode>();
|
||||
array<float> curve_mapping_curve;
|
||||
float min_x, max_x;
|
||||
curvemapping_float_to_array(mapping, curve_mapping_curve, RAMP_TABLE_SIZE);
|
||||
curvemapping_minmax(mapping, 1, &min_x, &max_x);
|
||||
curve->set_min_x(min_x);
|
||||
curve->set_max_x(max_x);
|
||||
curve->set_curve(curve_mapping_curve);
|
||||
node = curve;
|
||||
}
|
||||
else if (b_node.is_a(&RNA_ShaderNodeValToRGB)) {
|
||||
RGBRampNode *ramp = graph->create_node<RGBRampNode>();
|
||||
BL::ShaderNodeValToRGB b_ramp_node(b_node);
|
||||
|
@@ -171,12 +171,11 @@ static inline void curvemap_minmax_curve(/*const*/ BL::CurveMap &curve, float *m
|
||||
}
|
||||
|
||||
static inline void curvemapping_minmax(/*const*/ BL::CurveMapping &cumap,
|
||||
bool rgb_curve,
|
||||
int num_curves,
|
||||
float *min_x,
|
||||
float *max_x)
|
||||
{
|
||||
// const int num_curves = cumap.curves.length(); /* Gives linking error so far. */
|
||||
const int num_curves = rgb_curve ? 4 : 3;
|
||||
*min_x = FLT_MAX;
|
||||
*max_x = -FLT_MAX;
|
||||
for (int i = 0; i < num_curves; ++i) {
|
||||
@@ -196,6 +195,28 @@ static inline void curvemapping_to_array(BL::CurveMapping &cumap, array<float> &
|
||||
}
|
||||
}
|
||||
|
||||
static inline void curvemapping_float_to_array(BL::CurveMapping &cumap,
|
||||
array<float> &data,
|
||||
int size)
|
||||
{
|
||||
float min = 0.0f, max = 1.0f;
|
||||
|
||||
curvemapping_minmax(cumap, 1, &min, &max);
|
||||
|
||||
const float range = max - min;
|
||||
|
||||
cumap.update();
|
||||
|
||||
BL::CurveMap map = cumap.curves[0];
|
||||
|
||||
data.resize(size);
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
float t = min + (float)i / (float)(size - 1) * range;
|
||||
data[i] = cumap.evaluate(map, t);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void curvemapping_color_to_array(BL::CurveMapping &cumap,
|
||||
array<float3> &data,
|
||||
int size,
|
||||
@@ -214,7 +235,8 @@ static inline void curvemapping_color_to_array(BL::CurveMapping &cumap,
|
||||
*
|
||||
* There might be some better estimations here tho.
|
||||
*/
|
||||
curvemapping_minmax(cumap, rgb_curve, &min_x, &max_x);
|
||||
const int num_curves = rgb_curve ? 4 : 3;
|
||||
curvemapping_minmax(cumap, num_curves, &min_x, &max_x);
|
||||
|
||||
const float range_x = max_x - min_x;
|
||||
|
||||
|
@@ -37,14 +37,15 @@ CUDADeviceGraphicsInterop::~CUDADeviceGraphicsInterop()
|
||||
}
|
||||
}
|
||||
|
||||
void CUDADeviceGraphicsInterop::set_destination(
|
||||
const DeviceGraphicsInteropDestination &destination)
|
||||
void CUDADeviceGraphicsInterop::set_display_interop(
|
||||
const DisplayDriver::GraphicsInterop &display_interop)
|
||||
{
|
||||
const int64_t new_buffer_area = int64_t(destination.buffer_width) * destination.buffer_height;
|
||||
const int64_t new_buffer_area = int64_t(display_interop.buffer_width) *
|
||||
display_interop.buffer_height;
|
||||
|
||||
need_clear_ = destination.need_clear;
|
||||
need_clear_ = display_interop.need_clear;
|
||||
|
||||
if (opengl_pbo_id_ == destination.opengl_pbo_id && buffer_area_ == new_buffer_area) {
|
||||
if (opengl_pbo_id_ == display_interop.opengl_pbo_id && buffer_area_ == new_buffer_area) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -55,12 +56,12 @@ void CUDADeviceGraphicsInterop::set_destination(
|
||||
}
|
||||
|
||||
const CUresult result = cuGraphicsGLRegisterBuffer(
|
||||
&cu_graphics_resource_, destination.opengl_pbo_id, CU_GRAPHICS_MAP_RESOURCE_FLAGS_NONE);
|
||||
&cu_graphics_resource_, display_interop.opengl_pbo_id, CU_GRAPHICS_MAP_RESOURCE_FLAGS_NONE);
|
||||
if (result != CUDA_SUCCESS) {
|
||||
LOG(ERROR) << "Error registering OpenGL buffer: " << cuewErrorString(result);
|
||||
}
|
||||
|
||||
opengl_pbo_id_ = destination.opengl_pbo_id;
|
||||
opengl_pbo_id_ = display_interop.opengl_pbo_id;
|
||||
buffer_area_ = new_buffer_area;
|
||||
}
|
||||
|
||||
|
@@ -41,7 +41,7 @@ class CUDADeviceGraphicsInterop : public DeviceGraphicsInterop {
|
||||
CUDADeviceGraphicsInterop &operator=(const CUDADeviceGraphicsInterop &other) = delete;
|
||||
CUDADeviceGraphicsInterop &operator=(CUDADeviceGraphicsInterop &&other) = delete;
|
||||
|
||||
virtual void set_destination(const DeviceGraphicsInteropDestination &destination) override;
|
||||
virtual void set_display_interop(const DisplayDriver::GraphicsInterop &display_interop) override;
|
||||
|
||||
virtual device_ptr map() override;
|
||||
virtual void unmap() override;
|
||||
|
@@ -16,25 +16,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "render/display_driver.h"
|
||||
|
||||
#include "util/util_types.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
/* Information about interoperability destination.
|
||||
* Is provided by the GPUDisplay. */
|
||||
class DeviceGraphicsInteropDestination {
|
||||
public:
|
||||
/* Dimensions of the buffer, in pixels. */
|
||||
int buffer_width = 0;
|
||||
int buffer_height = 0;
|
||||
|
||||
/* OpenGL pixel buffer object. */
|
||||
int opengl_pbo_id = 0;
|
||||
|
||||
/* Clear the entire destination before doing partial write to it. */
|
||||
bool need_clear = false;
|
||||
};
|
||||
|
||||
/* Device-side graphics interoperability support.
|
||||
*
|
||||
* Takes care of holding all the handlers needed by the device to implement interoperability with
|
||||
@@ -46,7 +33,7 @@ class DeviceGraphicsInterop {
|
||||
|
||||
/* Update this device-side graphics interoperability object with the given destination resource
|
||||
* information. */
|
||||
virtual void set_destination(const DeviceGraphicsInteropDestination &destination) = 0;
|
||||
virtual void set_display_interop(const DisplayDriver::GraphicsInterop &display_interop) = 0;
|
||||
|
||||
virtual device_ptr map() = 0;
|
||||
virtual void unmap() = 0;
|
||||
|
@@ -37,11 +37,15 @@ HIPDeviceGraphicsInterop::~HIPDeviceGraphicsInterop()
|
||||
}
|
||||
}
|
||||
|
||||
void HIPDeviceGraphicsInterop::set_destination(const DeviceGraphicsInteropDestination &destination)
|
||||
void HIPDeviceGraphicsInterop::set_display_interop(
|
||||
const DisplayDriver::GraphicsInterop &display_interop)
|
||||
{
|
||||
const int64_t new_buffer_area = int64_t(destination.buffer_width) * destination.buffer_height;
|
||||
const int64_t new_buffer_area = int64_t(display_interop.buffer_width) *
|
||||
display_interop.buffer_height;
|
||||
|
||||
if (opengl_pbo_id_ == destination.opengl_pbo_id && buffer_area_ == new_buffer_area) {
|
||||
need_clear_ = display_interop.need_clear;
|
||||
|
||||
if (opengl_pbo_id_ == display_interop.opengl_pbo_id && buffer_area_ == new_buffer_area) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -52,12 +56,12 @@ void HIPDeviceGraphicsInterop::set_destination(const DeviceGraphicsInteropDestin
|
||||
}
|
||||
|
||||
const hipError_t result = hipGraphicsGLRegisterBuffer(
|
||||
&hip_graphics_resource_, destination.opengl_pbo_id, hipGraphicsRegisterFlagsNone);
|
||||
&hip_graphics_resource_, display_interop.opengl_pbo_id, hipGraphicsRegisterFlagsNone);
|
||||
if (result != hipSuccess) {
|
||||
LOG(ERROR) << "Error registering OpenGL buffer: " << hipewErrorString(result);
|
||||
}
|
||||
|
||||
opengl_pbo_id_ = destination.opengl_pbo_id;
|
||||
opengl_pbo_id_ = display_interop.opengl_pbo_id;
|
||||
buffer_area_ = new_buffer_area;
|
||||
}
|
||||
|
||||
@@ -77,6 +81,14 @@ device_ptr HIPDeviceGraphicsInterop::map()
|
||||
hip_device_assert(
|
||||
device_, hipGraphicsResourceGetMappedPointer(&hip_buffer, &bytes, hip_graphics_resource_));
|
||||
|
||||
if (need_clear_) {
|
||||
hip_device_assert(
|
||||
device_,
|
||||
hipMemsetD8Async(static_cast<hipDeviceptr_t>(hip_buffer), 0, bytes, queue_->stream()));
|
||||
|
||||
need_clear_ = false;
|
||||
}
|
||||
|
||||
return static_cast<device_ptr>(hip_buffer);
|
||||
}
|
||||
|
||||
|
@@ -39,7 +39,7 @@ class HIPDeviceGraphicsInterop : public DeviceGraphicsInterop {
|
||||
HIPDeviceGraphicsInterop &operator=(const HIPDeviceGraphicsInterop &other) = delete;
|
||||
HIPDeviceGraphicsInterop &operator=(HIPDeviceGraphicsInterop &&other) = delete;
|
||||
|
||||
virtual void set_destination(const DeviceGraphicsInteropDestination &destination) override;
|
||||
virtual void set_display_interop(const DisplayDriver::GraphicsInterop &display_interop) override;
|
||||
|
||||
virtual device_ptr map() override;
|
||||
virtual void unmap() override;
|
||||
@@ -53,6 +53,9 @@ class HIPDeviceGraphicsInterop : public DeviceGraphicsInterop {
|
||||
/* Buffer area in pixels of the corresponding PBO. */
|
||||
int64_t buffer_area_ = 0;
|
||||
|
||||
/* The destination was requested to be cleared. */
|
||||
bool need_clear_ = false;
|
||||
|
||||
hipGraphicsResource hip_graphics_resource_ = nullptr;
|
||||
};
|
||||
|
||||
|
@@ -28,7 +28,7 @@ void HIPDeviceKernels::load(HIPDevice *device)
|
||||
for (int i = 0; i < (int)DEVICE_KERNEL_NUM; i++) {
|
||||
HIPDeviceKernel &kernel = kernels_[i];
|
||||
|
||||
/* No megakernel used for GPU. */
|
||||
/* No mega-kernel used for GPU. */
|
||||
if (i == DEVICE_KERNEL_INTEGRATOR_MEGAKERNEL) {
|
||||
continue;
|
||||
}
|
||||
|
@@ -1419,7 +1419,7 @@ void OptiXDevice::build_bvh(BVH *bvh, Progress &progress, bool refit)
|
||||
}
|
||||
else {
|
||||
/* Can disable __anyhit__kernel_optix_visibility_test by default (except for thick curves,
|
||||
* since it needs to filter out endcaps there).
|
||||
* since it needs to filter out end-caps there).
|
||||
* It is enabled where necessary (visibility mask exceeds 8 bits or the other any-hit
|
||||
* programs like __anyhit__kernel_optix_shadow_all_hit) via OPTIX_RAY_FLAG_ENFORCE_ANYHIT.
|
||||
*/
|
||||
|
@@ -27,6 +27,8 @@ set(SRC
|
||||
pass_accessor.cpp
|
||||
pass_accessor_cpu.cpp
|
||||
pass_accessor_gpu.cpp
|
||||
path_trace_display.cpp
|
||||
path_trace_tile.cpp
|
||||
path_trace_work.cpp
|
||||
path_trace_work_cpu.cpp
|
||||
path_trace_work_gpu.cpp
|
||||
@@ -47,6 +49,8 @@ set(SRC_HEADERS
|
||||
pass_accessor.h
|
||||
pass_accessor_cpu.h
|
||||
pass_accessor_gpu.h
|
||||
path_trace_display.h
|
||||
path_trace_tile.h
|
||||
path_trace_work.h
|
||||
path_trace_work_cpu.h
|
||||
path_trace_work_gpu.h
|
||||
|
@@ -19,8 +19,9 @@
|
||||
#include "device/cpu/device.h"
|
||||
#include "device/device.h"
|
||||
#include "integrator/pass_accessor.h"
|
||||
#include "integrator/path_trace_display.h"
|
||||
#include "integrator/path_trace_tile.h"
|
||||
#include "integrator/render_scheduler.h"
|
||||
#include "render/gpu_display.h"
|
||||
#include "render/pass.h"
|
||||
#include "render/scene.h"
|
||||
#include "render/tile.h"
|
||||
@@ -67,11 +68,11 @@ PathTrace::PathTrace(Device *device,
|
||||
PathTrace::~PathTrace()
|
||||
{
|
||||
/* Destroy any GPU resource which was used for graphics interop.
|
||||
* Need to have access to the GPUDisplay as it is the only source of drawing context which is
|
||||
* used for interop. */
|
||||
if (gpu_display_) {
|
||||
* Need to have access to the PathTraceDisplay as it is the only source of drawing context which
|
||||
* is used for interop. */
|
||||
if (display_) {
|
||||
for (auto &&path_trace_work : path_trace_works_) {
|
||||
path_trace_work->destroy_gpu_resources(gpu_display_.get());
|
||||
path_trace_work->destroy_gpu_resources(display_.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -94,7 +95,7 @@ bool PathTrace::ready_to_reset()
|
||||
{
|
||||
/* The logic here is optimized for the best feedback in the viewport, which implies having a GPU
|
||||
* display. Of there is no such display, the logic here will break. */
|
||||
DCHECK(gpu_display_);
|
||||
DCHECK(display_);
|
||||
|
||||
/* The logic here tries to provide behavior which feels the most interactive feel to artists.
|
||||
* General idea is to be able to reset as quickly as possible, while still providing interactive
|
||||
@@ -126,8 +127,8 @@ void PathTrace::reset(const BufferParams &full_params, const BufferParams &big_t
|
||||
/* NOTE: GPU display checks for buffer modification and avoids unnecessary re-allocation.
|
||||
* It is requires to inform about reset whenever it happens, so that the redraw state tracking is
|
||||
* properly updated. */
|
||||
if (gpu_display_) {
|
||||
gpu_display_->reset(full_params);
|
||||
if (display_) {
|
||||
display_->reset(full_params);
|
||||
}
|
||||
|
||||
render_state_.has_denoised_result = false;
|
||||
@@ -535,25 +536,35 @@ void PathTrace::denoise(const RenderWork &render_work)
|
||||
render_scheduler_.report_denoise_time(render_work, time_dt() - start_time);
|
||||
}
|
||||
|
||||
void PathTrace::set_gpu_display(unique_ptr<GPUDisplay> gpu_display)
|
||||
void PathTrace::set_output_driver(unique_ptr<OutputDriver> driver)
|
||||
{
|
||||
gpu_display_ = move(gpu_display);
|
||||
output_driver_ = move(driver);
|
||||
}
|
||||
|
||||
void PathTrace::clear_gpu_display()
|
||||
void PathTrace::set_display_driver(unique_ptr<DisplayDriver> driver)
|
||||
{
|
||||
if (gpu_display_) {
|
||||
gpu_display_->clear();
|
||||
if (driver) {
|
||||
display_ = make_unique<PathTraceDisplay>(move(driver));
|
||||
}
|
||||
else {
|
||||
display_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void PathTrace::clear_display()
|
||||
{
|
||||
if (display_) {
|
||||
display_->clear();
|
||||
}
|
||||
}
|
||||
|
||||
void PathTrace::draw()
|
||||
{
|
||||
if (!gpu_display_) {
|
||||
if (!display_) {
|
||||
return;
|
||||
}
|
||||
|
||||
did_draw_after_reset_ |= gpu_display_->draw();
|
||||
did_draw_after_reset_ |= display_->draw();
|
||||
}
|
||||
|
||||
void PathTrace::update_display(const RenderWork &render_work)
|
||||
@@ -562,31 +573,32 @@ void PathTrace::update_display(const RenderWork &render_work)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!gpu_display_ && !tile_buffer_update_cb) {
|
||||
if (!display_ && !output_driver_) {
|
||||
VLOG(3) << "Ignore display update.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (full_params_.width == 0 || full_params_.height == 0) {
|
||||
VLOG(3) << "Skipping GPUDisplay update due to 0 size of the render buffer.";
|
||||
VLOG(3) << "Skipping PathTraceDisplay update due to 0 size of the render buffer.";
|
||||
return;
|
||||
}
|
||||
|
||||
const double start_time = time_dt();
|
||||
|
||||
if (tile_buffer_update_cb) {
|
||||
if (output_driver_) {
|
||||
VLOG(3) << "Invoke buffer update callback.";
|
||||
|
||||
tile_buffer_update_cb();
|
||||
PathTraceTile tile(*this);
|
||||
output_driver_->update_render_tile(tile);
|
||||
}
|
||||
|
||||
if (gpu_display_) {
|
||||
if (display_) {
|
||||
VLOG(3) << "Perform copy to GPUDisplay work.";
|
||||
|
||||
const int resolution_divider = render_work.resolution_divider;
|
||||
const int texture_width = max(1, full_params_.width / resolution_divider);
|
||||
const int texture_height = max(1, full_params_.height / resolution_divider);
|
||||
if (!gpu_display_->update_begin(texture_width, texture_height)) {
|
||||
if (!display_->update_begin(texture_width, texture_height)) {
|
||||
LOG(ERROR) << "Error beginning GPUDisplay update.";
|
||||
return;
|
||||
}
|
||||
@@ -600,10 +612,10 @@ void PathTrace::update_display(const RenderWork &render_work)
|
||||
* all works in parallel. */
|
||||
const int num_samples = get_num_samples_in_buffer();
|
||||
for (auto &&path_trace_work : path_trace_works_) {
|
||||
path_trace_work->copy_to_gpu_display(gpu_display_.get(), pass_mode, num_samples);
|
||||
path_trace_work->copy_to_display(display_.get(), pass_mode, num_samples);
|
||||
}
|
||||
|
||||
gpu_display_->update_end();
|
||||
display_->update_end();
|
||||
}
|
||||
|
||||
render_scheduler_.report_display_update_time(render_work, time_dt() - start_time);
|
||||
@@ -753,20 +765,26 @@ bool PathTrace::is_cancel_requested()
|
||||
|
||||
void PathTrace::tile_buffer_write()
|
||||
{
|
||||
if (!tile_buffer_write_cb) {
|
||||
if (!output_driver_) {
|
||||
return;
|
||||
}
|
||||
|
||||
tile_buffer_write_cb();
|
||||
PathTraceTile tile(*this);
|
||||
output_driver_->write_render_tile(tile);
|
||||
}
|
||||
|
||||
void PathTrace::tile_buffer_read()
|
||||
{
|
||||
if (!tile_buffer_read_cb) {
|
||||
if (!device_scene_->data.bake.use) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (tile_buffer_read_cb()) {
|
||||
if (!output_driver_) {
|
||||
return;
|
||||
}
|
||||
|
||||
PathTraceTile tile(*this);
|
||||
if (output_driver_->read_render_tile(tile)) {
|
||||
tbb::parallel_for_each(path_trace_works_, [](unique_ptr<PathTraceWork> &path_trace_work) {
|
||||
path_trace_work->copy_render_buffers_to_device();
|
||||
});
|
||||
@@ -1005,6 +1023,11 @@ int2 PathTrace::get_render_tile_offset() const
|
||||
return make_int2(tile.x, tile.y);
|
||||
}
|
||||
|
||||
int2 PathTrace::get_render_size() const
|
||||
{
|
||||
return tile_manager_.get_size();
|
||||
}
|
||||
|
||||
const BufferParams &PathTrace::get_render_tile_params() const
|
||||
{
|
||||
if (full_frame_state_.render_buffers) {
|
||||
|
@@ -31,12 +31,14 @@ CCL_NAMESPACE_BEGIN
|
||||
class AdaptiveSampling;
|
||||
class Device;
|
||||
class DeviceScene;
|
||||
class DisplayDriver;
|
||||
class Film;
|
||||
class RenderBuffers;
|
||||
class RenderScheduler;
|
||||
class RenderWork;
|
||||
class PathTraceDisplay;
|
||||
class OutputDriver;
|
||||
class Progress;
|
||||
class GPUDisplay;
|
||||
class TileManager;
|
||||
|
||||
/* PathTrace class takes care of kernel graph and scheduling on a (multi)device. It takes care of
|
||||
@@ -98,13 +100,16 @@ class PathTrace {
|
||||
* Use this to configure the adaptive sampler before rendering any samples. */
|
||||
void set_adaptive_sampling(const AdaptiveSampling &adaptive_sampling);
|
||||
|
||||
/* Set GPU display which takes care of drawing the render result. */
|
||||
void set_gpu_display(unique_ptr<GPUDisplay> gpu_display);
|
||||
/* Sets output driver for render buffer output. */
|
||||
void set_output_driver(unique_ptr<OutputDriver> driver);
|
||||
|
||||
/* Clear the GPU display by filling it in with all zeroes. */
|
||||
void clear_gpu_display();
|
||||
/* Set display driver for interactive render buffer display. */
|
||||
void set_display_driver(unique_ptr<DisplayDriver> driver);
|
||||
|
||||
/* Perform drawing of the current state of the GPUDisplay. */
|
||||
/* Clear the display buffer by filling it in with all zeroes. */
|
||||
void clear_display();
|
||||
|
||||
/* Perform drawing of the current state of the DisplayDriver. */
|
||||
void draw();
|
||||
|
||||
/* Cancel rendering process as soon as possible, without waiting for full tile to be sampled.
|
||||
@@ -157,6 +162,7 @@ class PathTrace {
|
||||
* instead. */
|
||||
int2 get_render_tile_size() const;
|
||||
int2 get_render_tile_offset() const;
|
||||
int2 get_render_size() const;
|
||||
|
||||
/* Get buffer parameters of the current tile.
|
||||
*
|
||||
@@ -168,18 +174,6 @@ class PathTrace {
|
||||
* times, and so on. */
|
||||
string full_report() const;
|
||||
|
||||
/* Callback which communicates an updates state of the render buffer of the current big tile.
|
||||
* Is called during path tracing to communicate work-in-progress state of the final buffer. */
|
||||
function<void(void)> tile_buffer_update_cb;
|
||||
|
||||
/* Callback which communicates final rendered buffer. Is called after path-tracing is done. */
|
||||
function<void(void)> tile_buffer_write_cb;
|
||||
|
||||
/* Callback which initializes rendered buffer. Is called before path-tracing starts.
|
||||
*
|
||||
* This is used for baking. */
|
||||
function<bool(void)> tile_buffer_read_cb;
|
||||
|
||||
/* Callback which is called to report current rendering progress.
|
||||
*
|
||||
* It is supposed to be cheaper than buffer update/write, hence can be called more often.
|
||||
@@ -252,7 +246,11 @@ class PathTrace {
|
||||
RenderScheduler &render_scheduler_;
|
||||
TileManager &tile_manager_;
|
||||
|
||||
unique_ptr<GPUDisplay> gpu_display_;
|
||||
/* Display driver for interactive render buffer display. */
|
||||
unique_ptr<PathTraceDisplay> display_;
|
||||
|
||||
/* Output driver to write render buffer to. */
|
||||
unique_ptr<OutputDriver> output_driver_;
|
||||
|
||||
/* Per-compute device descriptors of work which is responsible for path tracing on its configured
|
||||
* device. */
|
||||
|
@@ -14,20 +14,25 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "render/gpu_display.h"
|
||||
#include "integrator/path_trace_display.h"
|
||||
|
||||
#include "render/buffers.h"
|
||||
|
||||
#include "util/util_logging.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
void GPUDisplay::reset(const BufferParams &buffer_params)
|
||||
PathTraceDisplay::PathTraceDisplay(unique_ptr<DisplayDriver> driver) : driver_(move(driver))
|
||||
{
|
||||
}
|
||||
|
||||
void PathTraceDisplay::reset(const BufferParams &buffer_params)
|
||||
{
|
||||
thread_scoped_lock lock(mutex_);
|
||||
|
||||
const GPUDisplayParams old_params = params_;
|
||||
const DisplayDriver::Params old_params = params_;
|
||||
|
||||
params_.offset = make_int2(buffer_params.full_x, buffer_params.full_y);
|
||||
params_.full_offset = make_int2(buffer_params.full_x, buffer_params.full_y);
|
||||
params_.full_size = make_int2(buffer_params.full_width, buffer_params.full_height);
|
||||
params_.size = make_int2(buffer_params.width, buffer_params.height);
|
||||
|
||||
@@ -44,7 +49,7 @@ void GPUDisplay::reset(const BufferParams &buffer_params)
|
||||
texture_state_.is_outdated = true;
|
||||
}
|
||||
|
||||
void GPUDisplay::mark_texture_updated()
|
||||
void PathTraceDisplay::mark_texture_updated()
|
||||
{
|
||||
texture_state_.is_outdated = false;
|
||||
texture_state_.is_usable = true;
|
||||
@@ -54,7 +59,7 @@ void GPUDisplay::mark_texture_updated()
|
||||
* Update procedure.
|
||||
*/
|
||||
|
||||
bool GPUDisplay::update_begin(int texture_width, int texture_height)
|
||||
bool PathTraceDisplay::update_begin(int texture_width, int texture_height)
|
||||
{
|
||||
DCHECK(!update_state_.is_active);
|
||||
|
||||
@@ -66,15 +71,15 @@ bool GPUDisplay::update_begin(int texture_width, int texture_height)
|
||||
/* Get parameters within a mutex lock, to avoid reset() modifying them at the same time.
|
||||
* The update itself is non-blocking however, for better performance and to avoid
|
||||
* potential deadlocks due to locks held by the subclass. */
|
||||
GPUDisplayParams params;
|
||||
DisplayDriver::Params params;
|
||||
{
|
||||
thread_scoped_lock lock(mutex_);
|
||||
params = params_;
|
||||
texture_state_.size = make_int2(texture_width, texture_height);
|
||||
}
|
||||
|
||||
if (!do_update_begin(params, texture_width, texture_height)) {
|
||||
LOG(ERROR) << "GPUDisplay implementation could not begin update.";
|
||||
if (!driver_->update_begin(params, texture_width, texture_height)) {
|
||||
LOG(ERROR) << "PathTraceDisplay implementation could not begin update.";
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -83,7 +88,7 @@ bool GPUDisplay::update_begin(int texture_width, int texture_height)
|
||||
return true;
|
||||
}
|
||||
|
||||
void GPUDisplay::update_end()
|
||||
void PathTraceDisplay::update_end()
|
||||
{
|
||||
DCHECK(update_state_.is_active);
|
||||
|
||||
@@ -92,12 +97,12 @@ void GPUDisplay::update_end()
|
||||
return;
|
||||
}
|
||||
|
||||
do_update_end();
|
||||
driver_->update_end();
|
||||
|
||||
update_state_.is_active = false;
|
||||
}
|
||||
|
||||
int2 GPUDisplay::get_texture_size() const
|
||||
int2 PathTraceDisplay::get_texture_size() const
|
||||
{
|
||||
return texture_state_.size;
|
||||
}
|
||||
@@ -106,25 +111,54 @@ int2 GPUDisplay::get_texture_size() const
|
||||
* Texture update from CPU buffer.
|
||||
*/
|
||||
|
||||
void GPUDisplay::copy_pixels_to_texture(
|
||||
void PathTraceDisplay::copy_pixels_to_texture(
|
||||
const half4 *rgba_pixels, int texture_x, int texture_y, int pixels_width, int pixels_height)
|
||||
{
|
||||
DCHECK(update_state_.is_active);
|
||||
|
||||
if (!update_state_.is_active) {
|
||||
LOG(ERROR) << "Attempt to copy pixels data outside of GPUDisplay update.";
|
||||
LOG(ERROR) << "Attempt to copy pixels data outside of PathTraceDisplay update.";
|
||||
return;
|
||||
}
|
||||
|
||||
mark_texture_updated();
|
||||
do_copy_pixels_to_texture(rgba_pixels, texture_x, texture_y, pixels_width, pixels_height);
|
||||
|
||||
/* This call copies pixels to a mapped texture buffer which is typically much cheaper from CPU
|
||||
* time point of view than to copy data directly to a texture.
|
||||
*
|
||||
* The possible downside of this approach is that it might require a higher peak memory when
|
||||
* doing partial updates of the texture (although, in practice even partial updates might peak
|
||||
* with a full-frame buffer stored on the CPU if the GPU is currently occupied). */
|
||||
half4 *mapped_rgba_pixels = map_texture_buffer();
|
||||
if (!mapped_rgba_pixels) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int texture_width = texture_state_.size.x;
|
||||
const int texture_height = texture_state_.size.y;
|
||||
|
||||
if (texture_x == 0 && texture_y == 0 && pixels_width == texture_width &&
|
||||
pixels_height == texture_height) {
|
||||
const size_t size_in_bytes = sizeof(half4) * texture_width * texture_height;
|
||||
memcpy(mapped_rgba_pixels, rgba_pixels, size_in_bytes);
|
||||
}
|
||||
else {
|
||||
const half4 *rgba_row = rgba_pixels;
|
||||
half4 *mapped_rgba_row = mapped_rgba_pixels + texture_y * texture_width + texture_x;
|
||||
for (int y = 0; y < pixels_height;
|
||||
++y, rgba_row += pixels_width, mapped_rgba_row += texture_width) {
|
||||
memcpy(mapped_rgba_row, rgba_row, sizeof(half4) * pixels_width);
|
||||
}
|
||||
}
|
||||
|
||||
unmap_texture_buffer();
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* Texture buffer mapping.
|
||||
*/
|
||||
|
||||
half4 *GPUDisplay::map_texture_buffer()
|
||||
half4 *PathTraceDisplay::map_texture_buffer()
|
||||
{
|
||||
DCHECK(!texture_buffer_state_.is_mapped);
|
||||
DCHECK(update_state_.is_active);
|
||||
@@ -135,11 +169,11 @@ half4 *GPUDisplay::map_texture_buffer()
|
||||
}
|
||||
|
||||
if (!update_state_.is_active) {
|
||||
LOG(ERROR) << "Attempt to copy pixels data outside of GPUDisplay update.";
|
||||
LOG(ERROR) << "Attempt to copy pixels data outside of PathTraceDisplay update.";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
half4 *mapped_rgba_pixels = do_map_texture_buffer();
|
||||
half4 *mapped_rgba_pixels = driver_->map_texture_buffer();
|
||||
|
||||
if (mapped_rgba_pixels) {
|
||||
texture_buffer_state_.is_mapped = true;
|
||||
@@ -148,7 +182,7 @@ half4 *GPUDisplay::map_texture_buffer()
|
||||
return mapped_rgba_pixels;
|
||||
}
|
||||
|
||||
void GPUDisplay::unmap_texture_buffer()
|
||||
void PathTraceDisplay::unmap_texture_buffer()
|
||||
{
|
||||
DCHECK(texture_buffer_state_.is_mapped);
|
||||
|
||||
@@ -160,14 +194,14 @@ void GPUDisplay::unmap_texture_buffer()
|
||||
texture_buffer_state_.is_mapped = false;
|
||||
|
||||
mark_texture_updated();
|
||||
do_unmap_texture_buffer();
|
||||
driver_->unmap_texture_buffer();
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* Graphics interoperability.
|
||||
*/
|
||||
|
||||
DeviceGraphicsInteropDestination GPUDisplay::graphics_interop_get()
|
||||
DisplayDriver::GraphicsInterop PathTraceDisplay::graphics_interop_get()
|
||||
{
|
||||
DCHECK(!texture_buffer_state_.is_mapped);
|
||||
DCHECK(update_state_.is_active);
|
||||
@@ -175,38 +209,45 @@ DeviceGraphicsInteropDestination GPUDisplay::graphics_interop_get()
|
||||
if (texture_buffer_state_.is_mapped) {
|
||||
LOG(ERROR)
|
||||
<< "Attempt to use graphics interoperability mode while the texture buffer is mapped.";
|
||||
return DeviceGraphicsInteropDestination();
|
||||
return DisplayDriver::GraphicsInterop();
|
||||
}
|
||||
|
||||
if (!update_state_.is_active) {
|
||||
LOG(ERROR) << "Attempt to use graphics interoperability outside of GPUDisplay update.";
|
||||
return DeviceGraphicsInteropDestination();
|
||||
LOG(ERROR) << "Attempt to use graphics interoperability outside of PathTraceDisplay update.";
|
||||
return DisplayDriver::GraphicsInterop();
|
||||
}
|
||||
|
||||
/* Assume that interop will write new values to the texture. */
|
||||
mark_texture_updated();
|
||||
|
||||
return do_graphics_interop_get();
|
||||
return driver_->graphics_interop_get();
|
||||
}
|
||||
|
||||
void GPUDisplay::graphics_interop_activate()
|
||||
void PathTraceDisplay::graphics_interop_activate()
|
||||
{
|
||||
driver_->graphics_interop_activate();
|
||||
}
|
||||
|
||||
void GPUDisplay::graphics_interop_deactivate()
|
||||
void PathTraceDisplay::graphics_interop_deactivate()
|
||||
{
|
||||
driver_->graphics_interop_deactivate();
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* Drawing.
|
||||
*/
|
||||
|
||||
bool GPUDisplay::draw()
|
||||
void PathTraceDisplay::clear()
|
||||
{
|
||||
driver_->clear();
|
||||
}
|
||||
|
||||
bool PathTraceDisplay::draw()
|
||||
{
|
||||
/* Get parameters within a mutex lock, to avoid reset() modifying them at the same time.
|
||||
* The drawing itself is non-blocking however, for better performance and to avoid
|
||||
* potential deadlocks due to locks held by the subclass. */
|
||||
GPUDisplayParams params;
|
||||
DisplayDriver::Params params;
|
||||
bool is_usable;
|
||||
bool is_outdated;
|
||||
|
||||
@@ -218,7 +259,7 @@ bool GPUDisplay::draw()
|
||||
}
|
||||
|
||||
if (is_usable) {
|
||||
do_draw(params);
|
||||
driver_->draw(params);
|
||||
}
|
||||
|
||||
return !is_outdated;
|
@@ -16,52 +16,30 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "device/device_graphics_interop.h"
|
||||
#include "render/display_driver.h"
|
||||
|
||||
#include "util/util_half.h"
|
||||
#include "util/util_thread.h"
|
||||
#include "util/util_types.h"
|
||||
#include "util/util_unique_ptr.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
class BufferParams;
|
||||
|
||||
/* GPUDisplay class takes care of drawing render result in a viewport. The render result is stored
|
||||
* in a GPU-side texture, which is updated from a path tracer and drawn by an application.
|
||||
/* PathTraceDisplay is used for efficient render buffer display.
|
||||
*
|
||||
* The base GPUDisplay does some special texture state tracking, which allows render Session to
|
||||
* make decisions on whether reset for an updated state is possible or not. This state should only
|
||||
* be tracked in a base class and a particular implementation should not worry about it.
|
||||
* The host applications implements a DisplayDriver, storing a render pass in a GPU-side
|
||||
* textures. This texture is continuously updated by the path tracer and drawn by the host
|
||||
* application.
|
||||
*
|
||||
* The subclasses should only implement the pure virtual methods, which allows them to not worry
|
||||
* about parent method calls, which helps them to be as small and reliable as possible. */
|
||||
* PathTraceDisplay is a wrapper around the DisplayDriver, adding thread safety, state tracking
|
||||
* and error checking. */
|
||||
|
||||
class GPUDisplayParams {
|
||||
class PathTraceDisplay {
|
||||
public:
|
||||
/* Offset of the display within a viewport.
|
||||
* For example, set to a lower-bottom corner of border render in Blender's viewport. */
|
||||
int2 offset = make_int2(0, 0);
|
||||
|
||||
/* Full viewport size.
|
||||
*
|
||||
* NOTE: Is not affected by the resolution divider. */
|
||||
int2 full_size = make_int2(0, 0);
|
||||
|
||||
/* Effective viewport size.
|
||||
* In the case of border render, size of the border rectangle.
|
||||
*
|
||||
* NOTE: Is not affected by the resolution divider. */
|
||||
int2 size = make_int2(0, 0);
|
||||
|
||||
bool modified(const GPUDisplayParams &other) const
|
||||
{
|
||||
return !(offset == other.offset && full_size == other.full_size && size == other.size);
|
||||
}
|
||||
};
|
||||
|
||||
class GPUDisplay {
|
||||
public:
|
||||
GPUDisplay() = default;
|
||||
virtual ~GPUDisplay() = default;
|
||||
PathTraceDisplay(unique_ptr<DisplayDriver> driver);
|
||||
virtual ~PathTraceDisplay() = default;
|
||||
|
||||
/* Reset the display for the new state of render session. Is called whenever session is reset,
|
||||
* which happens on changes like viewport navigation or viewport dimension change.
|
||||
@@ -69,11 +47,6 @@ class GPUDisplay {
|
||||
* This call will configure parameters for a changed buffer and reset the texture state. */
|
||||
void reset(const BufferParams &buffer_params);
|
||||
|
||||
const GPUDisplayParams &get_params() const
|
||||
{
|
||||
return params_;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* Update procedure.
|
||||
*
|
||||
@@ -94,7 +67,8 @@ class GPUDisplay {
|
||||
/* --------------------------------------------------------------------
|
||||
* Texture update from CPU buffer.
|
||||
*
|
||||
* NOTE: The GPUDisplay should be marked for an update being in process with `update_begin()`.
|
||||
* NOTE: The PathTraceDisplay should be marked for an update being in process with
|
||||
* `update_begin()`.
|
||||
*
|
||||
* Most portable implementation, which must be supported by all platforms. Might not be the most
|
||||
* efficient one.
|
||||
@@ -115,7 +89,8 @@ class GPUDisplay {
|
||||
* This functionality is used to update GPU-side texture content without need to maintain CPU
|
||||
* side buffer on the caller.
|
||||
*
|
||||
* NOTE: The GPUDisplay should be marked for an update being in process with `update_begin()`.
|
||||
* NOTE: The PathTraceDisplay should be marked for an update being in process with
|
||||
* `update_begin()`.
|
||||
*
|
||||
* NOTE: Texture buffer can not be mapped while graphics interoperability is active. This means
|
||||
* that `map_texture_buffer()` is not allowed between `graphics_interop_begin()` and
|
||||
@@ -145,14 +120,14 @@ class GPUDisplay {
|
||||
* that `graphics_interop_get()` is not allowed between `map_texture_buffer()` and
|
||||
* `unmap_texture_buffer()` calls. */
|
||||
|
||||
/* Get GPUDisplay graphics interoperability information which acts as a destination for the
|
||||
/* Get PathTraceDisplay graphics interoperability information which acts as a destination for the
|
||||
* device API. */
|
||||
DeviceGraphicsInteropDestination graphics_interop_get();
|
||||
DisplayDriver::GraphicsInterop graphics_interop_get();
|
||||
|
||||
/* (De)activate GPU display for graphics interoperability outside of regular display update
|
||||
* routines. */
|
||||
virtual void graphics_interop_activate();
|
||||
virtual void graphics_interop_deactivate();
|
||||
void graphics_interop_activate();
|
||||
void graphics_interop_deactivate();
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* Drawing.
|
||||
@@ -168,42 +143,21 @@ class GPUDisplay {
|
||||
* after clear will write new pixel values for an updating area, leaving everything else zeroed.
|
||||
*
|
||||
* If the GPU display supports graphics interoperability then the zeroing the display is to be
|
||||
* delegated to the device via the `DeviceGraphicsInteropDestination`. */
|
||||
virtual void clear() = 0;
|
||||
* delegated to the device via the `DisplayDriver::GraphicsInterop`. */
|
||||
void clear();
|
||||
|
||||
/* Draw the current state of the texture.
|
||||
*
|
||||
* Returns true if this call did draw an updated state of the texture. */
|
||||
bool draw();
|
||||
|
||||
protected:
|
||||
/* Implementation-specific calls which subclasses are to implement.
|
||||
* These `do_foo()` method corresponds to their `foo()` calls, but they are purely virtual to
|
||||
* simplify their particular implementation. */
|
||||
virtual bool do_update_begin(const GPUDisplayParams ¶ms,
|
||||
int texture_width,
|
||||
int texture_height) = 0;
|
||||
virtual void do_update_end() = 0;
|
||||
|
||||
virtual void do_copy_pixels_to_texture(const half4 *rgba_pixels,
|
||||
int texture_x,
|
||||
int texture_y,
|
||||
int pixels_width,
|
||||
int pixels_height) = 0;
|
||||
|
||||
virtual half4 *do_map_texture_buffer() = 0;
|
||||
virtual void do_unmap_texture_buffer() = 0;
|
||||
|
||||
/* Note that this might be called in parallel to do_update_begin() and do_update_end(),
|
||||
* the subclass is responsible for appropriate mutex locks to avoid multiple threads
|
||||
* editing and drawing the texture at the same time. */
|
||||
virtual void do_draw(const GPUDisplayParams ¶ms) = 0;
|
||||
|
||||
virtual DeviceGraphicsInteropDestination do_graphics_interop_get() = 0;
|
||||
|
||||
private:
|
||||
/* Display driver implemented by the host application. */
|
||||
unique_ptr<DisplayDriver> driver_;
|
||||
|
||||
/* Current display parameters */
|
||||
thread_mutex mutex_;
|
||||
GPUDisplayParams params_;
|
||||
DisplayDriver::Params params_;
|
||||
|
||||
/* Mark texture as its content has been updated.
|
||||
* Used from places which knows that the texture content has been brought up-to-date, so that the
|
107
intern/cycles/integrator/path_trace_tile.cpp
Normal file
107
intern/cycles/integrator/path_trace_tile.cpp
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright 2021 Blender Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "integrator/path_trace_tile.h"
|
||||
#include "integrator/pass_accessor_cpu.h"
|
||||
#include "integrator/path_trace.h"
|
||||
|
||||
#include "render/buffers.h"
|
||||
#include "render/film.h"
|
||||
#include "render/pass.h"
|
||||
#include "render/scene.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
PathTraceTile::PathTraceTile(PathTrace &path_trace)
|
||||
: OutputDriver::Tile(path_trace.get_render_tile_offset(),
|
||||
path_trace.get_render_tile_size(),
|
||||
path_trace.get_render_size(),
|
||||
path_trace.get_render_tile_params().layer,
|
||||
path_trace.get_render_tile_params().view),
|
||||
path_trace_(path_trace),
|
||||
copied_from_device_(false)
|
||||
{
|
||||
}
|
||||
|
||||
bool PathTraceTile::get_pass_pixels(const string_view pass_name,
|
||||
const int num_channels,
|
||||
float *pixels) const
|
||||
{
|
||||
/* NOTE: The code relies on a fact that session is fully update and no scene/buffer modification
|
||||
* is happening while this function runs. */
|
||||
|
||||
if (!copied_from_device_) {
|
||||
/* Copy from device on demand. */
|
||||
path_trace_.copy_render_tile_from_device();
|
||||
const_cast<PathTraceTile *>(this)->copied_from_device_ = true;
|
||||
}
|
||||
|
||||
const BufferParams &buffer_params = path_trace_.get_render_tile_params();
|
||||
|
||||
const BufferPass *pass = buffer_params.find_pass(pass_name);
|
||||
if (pass == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool has_denoised_result = path_trace_.has_denoised_result();
|
||||
if (pass->mode == PassMode::DENOISED && !has_denoised_result) {
|
||||
pass = buffer_params.find_pass(pass->type);
|
||||
if (pass == nullptr) {
|
||||
/* Happens when denoised result pass is requested but is never written by the kernel. */
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
pass = buffer_params.get_actual_display_pass(pass);
|
||||
|
||||
const float exposure = buffer_params.exposure;
|
||||
const int num_samples = path_trace_.get_num_render_tile_samples();
|
||||
|
||||
PassAccessor::PassAccessInfo pass_access_info(*pass);
|
||||
pass_access_info.use_approximate_shadow_catcher = buffer_params.use_approximate_shadow_catcher;
|
||||
pass_access_info.use_approximate_shadow_catcher_background =
|
||||
pass_access_info.use_approximate_shadow_catcher && !buffer_params.use_transparent_background;
|
||||
|
||||
const PassAccessorCPU pass_accessor(pass_access_info, exposure, num_samples);
|
||||
const PassAccessor::Destination destination(pixels, num_channels);
|
||||
|
||||
return path_trace_.get_render_tile_pixels(pass_accessor, destination);
|
||||
}
|
||||
|
||||
bool PathTraceTile::set_pass_pixels(const string_view pass_name,
|
||||
const int num_channels,
|
||||
const float *pixels) const
|
||||
{
|
||||
/* NOTE: The code relies on a fact that session is fully update and no scene/buffer modification
|
||||
* is happening while this function runs. */
|
||||
|
||||
const BufferParams &buffer_params = path_trace_.get_render_tile_params();
|
||||
const BufferPass *pass = buffer_params.find_pass(pass_name);
|
||||
if (!pass) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const float exposure = buffer_params.exposure;
|
||||
const int num_samples = 1;
|
||||
|
||||
const PassAccessor::PassAccessInfo pass_access_info(*pass);
|
||||
PassAccessorCPU pass_accessor(pass_access_info, exposure, num_samples);
|
||||
PassAccessor::Source source(pixels, num_channels);
|
||||
|
||||
return path_trace_.set_render_tile_pixels(pass_accessor, source);
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
43
intern/cycles/integrator/path_trace_tile.h
Normal file
43
intern/cycles/integrator/path_trace_tile.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2021 Blender Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "render/output_driver.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
/* PathTraceTile
|
||||
*
|
||||
* Implementation of OutputDriver::Tile interface for path tracer. */
|
||||
|
||||
class PathTrace;
|
||||
|
||||
class PathTraceTile : public OutputDriver::Tile {
|
||||
public:
|
||||
PathTraceTile(PathTrace &path_trace);
|
||||
|
||||
bool get_pass_pixels(const string_view pass_name, const int num_channels, float *pixels) const;
|
||||
bool set_pass_pixels(const string_view pass_name,
|
||||
const int num_channels,
|
||||
const float *pixels) const;
|
||||
|
||||
private:
|
||||
PathTrace &path_trace_;
|
||||
bool copied_from_device_;
|
||||
};
|
||||
|
||||
CCL_NAMESPACE_END
|
@@ -16,12 +16,12 @@
|
||||
|
||||
#include "device/device.h"
|
||||
|
||||
#include "integrator/path_trace_display.h"
|
||||
#include "integrator/path_trace_work.h"
|
||||
#include "integrator/path_trace_work_cpu.h"
|
||||
#include "integrator/path_trace_work_gpu.h"
|
||||
#include "render/buffers.h"
|
||||
#include "render/film.h"
|
||||
#include "render/gpu_display.h"
|
||||
#include "render/scene.h"
|
||||
|
||||
#include "kernel/kernel_types.h"
|
||||
@@ -185,12 +185,12 @@ PassAccessor::PassAccessInfo PathTraceWork::get_display_pass_access_info(PassMod
|
||||
return pass_access_info;
|
||||
}
|
||||
|
||||
PassAccessor::Destination PathTraceWork::get_gpu_display_destination_template(
|
||||
const GPUDisplay *gpu_display) const
|
||||
PassAccessor::Destination PathTraceWork::get_display_destination_template(
|
||||
const PathTraceDisplay *display) const
|
||||
{
|
||||
PassAccessor::Destination destination(film_->get_display_pass());
|
||||
|
||||
const int2 display_texture_size = gpu_display->get_texture_size();
|
||||
const int2 display_texture_size = display->get_texture_size();
|
||||
const int texture_x = effective_buffer_params_.full_x - effective_full_params_.full_x;
|
||||
const int texture_y = effective_buffer_params_.full_y - effective_full_params_.full_y;
|
||||
|
||||
|
@@ -28,7 +28,7 @@ class BufferParams;
|
||||
class Device;
|
||||
class DeviceScene;
|
||||
class Film;
|
||||
class GPUDisplay;
|
||||
class PathTraceDisplay;
|
||||
class RenderBuffers;
|
||||
|
||||
class PathTraceWork {
|
||||
@@ -83,11 +83,9 @@ class PathTraceWork {
|
||||
* noisy pass mode will be passed here when it is known that the buffer does not have denoised
|
||||
* passes yet (because denoiser did not run). If the denoised pass is requested and denoiser is
|
||||
* not used then this function will fall-back to the noisy pass instead. */
|
||||
virtual void copy_to_gpu_display(GPUDisplay *gpu_display,
|
||||
PassMode pass_mode,
|
||||
int num_samples) = 0;
|
||||
virtual void copy_to_display(PathTraceDisplay *display, PassMode pass_mode, int num_samples) = 0;
|
||||
|
||||
virtual void destroy_gpu_resources(GPUDisplay *gpu_display) = 0;
|
||||
virtual void destroy_gpu_resources(PathTraceDisplay *display) = 0;
|
||||
|
||||
/* Copy data from/to given render buffers.
|
||||
* Will copy pixels from a corresponding place (from multi-device point of view) of the render
|
||||
@@ -162,8 +160,8 @@ class PathTraceWork {
|
||||
|
||||
/* Get destination which offset and stride are configured so that writing to it will write to a
|
||||
* proper location of GPU display texture, taking current tile and device slice into account. */
|
||||
PassAccessor::Destination get_gpu_display_destination_template(
|
||||
const GPUDisplay *gpu_display) const;
|
||||
PassAccessor::Destination get_display_destination_template(
|
||||
const PathTraceDisplay *display) const;
|
||||
|
||||
/* Device which will be used for path tracing.
|
||||
* Note that it is an actual render device (and never is a multi-device). */
|
||||
|
@@ -22,9 +22,9 @@
|
||||
#include "kernel/kernel_path_state.h"
|
||||
|
||||
#include "integrator/pass_accessor_cpu.h"
|
||||
#include "integrator/path_trace_display.h"
|
||||
|
||||
#include "render/buffers.h"
|
||||
#include "render/gpu_display.h"
|
||||
#include "render/scene.h"
|
||||
|
||||
#include "util/util_atomic.h"
|
||||
@@ -161,14 +161,14 @@ void PathTraceWorkCPU::render_samples_full_pipeline(KernelGlobals *kernel_global
|
||||
}
|
||||
}
|
||||
|
||||
void PathTraceWorkCPU::copy_to_gpu_display(GPUDisplay *gpu_display,
|
||||
PassMode pass_mode,
|
||||
int num_samples)
|
||||
void PathTraceWorkCPU::copy_to_display(PathTraceDisplay *display,
|
||||
PassMode pass_mode,
|
||||
int num_samples)
|
||||
{
|
||||
half4 *rgba_half = gpu_display->map_texture_buffer();
|
||||
half4 *rgba_half = display->map_texture_buffer();
|
||||
if (!rgba_half) {
|
||||
/* TODO(sergey): Look into using copy_to_gpu_display() if mapping failed. Might be needed for
|
||||
* some implementations of GPUDisplay which can not map memory? */
|
||||
/* TODO(sergey): Look into using copy_to_display() if mapping failed. Might be needed for
|
||||
* some implementations of PathTraceDisplay which can not map memory? */
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -178,7 +178,7 @@ void PathTraceWorkCPU::copy_to_gpu_display(GPUDisplay *gpu_display,
|
||||
|
||||
const PassAccessorCPU pass_accessor(pass_access_info, kfilm.exposure, num_samples);
|
||||
|
||||
PassAccessor::Destination destination = get_gpu_display_destination_template(gpu_display);
|
||||
PassAccessor::Destination destination = get_display_destination_template(display);
|
||||
destination.pixels_half_rgba = rgba_half;
|
||||
|
||||
tbb::task_arena local_arena = local_tbb_arena_create(device_);
|
||||
@@ -186,10 +186,10 @@ void PathTraceWorkCPU::copy_to_gpu_display(GPUDisplay *gpu_display,
|
||||
pass_accessor.get_render_tile_pixels(buffers_.get(), effective_buffer_params_, destination);
|
||||
});
|
||||
|
||||
gpu_display->unmap_texture_buffer();
|
||||
display->unmap_texture_buffer();
|
||||
}
|
||||
|
||||
void PathTraceWorkCPU::destroy_gpu_resources(GPUDisplay * /*gpu_display*/)
|
||||
void PathTraceWorkCPU::destroy_gpu_resources(PathTraceDisplay * /*display*/)
|
||||
{
|
||||
}
|
||||
|
||||
|
@@ -50,10 +50,10 @@ class PathTraceWorkCPU : public PathTraceWork {
|
||||
int start_sample,
|
||||
int samples_num) override;
|
||||
|
||||
virtual void copy_to_gpu_display(GPUDisplay *gpu_display,
|
||||
PassMode pass_mode,
|
||||
int num_samples) override;
|
||||
virtual void destroy_gpu_resources(GPUDisplay *gpu_display) override;
|
||||
virtual void copy_to_display(PathTraceDisplay *display,
|
||||
PassMode pass_mode,
|
||||
int num_samples) override;
|
||||
virtual void destroy_gpu_resources(PathTraceDisplay *display) override;
|
||||
|
||||
virtual bool copy_render_buffers_from_device() override;
|
||||
virtual bool copy_render_buffers_to_device() override;
|
||||
|
@@ -15,12 +15,12 @@
|
||||
*/
|
||||
|
||||
#include "integrator/path_trace_work_gpu.h"
|
||||
#include "integrator/path_trace_display.h"
|
||||
|
||||
#include "device/device.h"
|
||||
|
||||
#include "integrator/pass_accessor_gpu.h"
|
||||
#include "render/buffers.h"
|
||||
#include "render/gpu_display.h"
|
||||
#include "render/scene.h"
|
||||
#include "util/util_logging.h"
|
||||
#include "util/util_tbb.h"
|
||||
@@ -46,7 +46,7 @@ PathTraceWorkGPU::PathTraceWorkGPU(Device *device,
|
||||
queued_paths_(device, "queued_paths", MEM_READ_WRITE),
|
||||
num_queued_paths_(device, "num_queued_paths", MEM_READ_WRITE),
|
||||
work_tiles_(device, "work_tiles", MEM_READ_WRITE),
|
||||
gpu_display_rgba_half_(device, "display buffer half", MEM_READ_WRITE),
|
||||
display_rgba_half_(device, "display buffer half", MEM_READ_WRITE),
|
||||
max_num_paths_(queue_->num_concurrent_states(sizeof(IntegratorStateCPU))),
|
||||
min_num_active_paths_(queue_->num_concurrent_busy_states()),
|
||||
max_active_path_index_(0)
|
||||
@@ -652,7 +652,7 @@ int PathTraceWorkGPU::get_num_active_paths()
|
||||
bool PathTraceWorkGPU::should_use_graphics_interop()
|
||||
{
|
||||
/* There are few aspects with the graphics interop when using multiple devices caused by the fact
|
||||
* that the GPUDisplay has a single texture:
|
||||
* that the PathTraceDisplay has a single texture:
|
||||
*
|
||||
* CUDA will return `CUDA_ERROR_NOT_SUPPORTED` from `cuGraphicsGLRegisterBuffer()` when
|
||||
* attempting to register OpenGL PBO which has been mapped. Which makes sense, because
|
||||
@@ -678,9 +678,9 @@ bool PathTraceWorkGPU::should_use_graphics_interop()
|
||||
return interop_use_;
|
||||
}
|
||||
|
||||
void PathTraceWorkGPU::copy_to_gpu_display(GPUDisplay *gpu_display,
|
||||
PassMode pass_mode,
|
||||
int num_samples)
|
||||
void PathTraceWorkGPU::copy_to_display(PathTraceDisplay *display,
|
||||
PassMode pass_mode,
|
||||
int num_samples)
|
||||
{
|
||||
if (device_->have_error()) {
|
||||
/* Don't attempt to update GPU display if the device has errors: the error state will make
|
||||
@@ -694,7 +694,7 @@ void PathTraceWorkGPU::copy_to_gpu_display(GPUDisplay *gpu_display,
|
||||
}
|
||||
|
||||
if (should_use_graphics_interop()) {
|
||||
if (copy_to_gpu_display_interop(gpu_display, pass_mode, num_samples)) {
|
||||
if (copy_to_display_interop(display, pass_mode, num_samples)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -703,12 +703,12 @@ void PathTraceWorkGPU::copy_to_gpu_display(GPUDisplay *gpu_display,
|
||||
interop_use_ = false;
|
||||
}
|
||||
|
||||
copy_to_gpu_display_naive(gpu_display, pass_mode, num_samples);
|
||||
copy_to_display_naive(display, pass_mode, num_samples);
|
||||
}
|
||||
|
||||
void PathTraceWorkGPU::copy_to_gpu_display_naive(GPUDisplay *gpu_display,
|
||||
PassMode pass_mode,
|
||||
int num_samples)
|
||||
void PathTraceWorkGPU::copy_to_display_naive(PathTraceDisplay *display,
|
||||
PassMode pass_mode,
|
||||
int num_samples)
|
||||
{
|
||||
const int full_x = effective_buffer_params_.full_x;
|
||||
const int full_y = effective_buffer_params_.full_y;
|
||||
@@ -725,44 +725,42 @@ void PathTraceWorkGPU::copy_to_gpu_display_naive(GPUDisplay *gpu_display,
|
||||
* NOTE: allocation happens to the final resolution so that no re-allocation happens on every
|
||||
* change of the resolution divider. However, if the display becomes smaller, shrink the
|
||||
* allocated memory as well. */
|
||||
if (gpu_display_rgba_half_.data_width != final_width ||
|
||||
gpu_display_rgba_half_.data_height != final_height) {
|
||||
gpu_display_rgba_half_.alloc(final_width, final_height);
|
||||
if (display_rgba_half_.data_width != final_width ||
|
||||
display_rgba_half_.data_height != final_height) {
|
||||
display_rgba_half_.alloc(final_width, final_height);
|
||||
/* TODO(sergey): There should be a way to make sure device-side memory is allocated without
|
||||
* transferring zeroes to the device. */
|
||||
queue_->zero_to_device(gpu_display_rgba_half_);
|
||||
queue_->zero_to_device(display_rgba_half_);
|
||||
}
|
||||
|
||||
PassAccessor::Destination destination(film_->get_display_pass());
|
||||
destination.d_pixels_half_rgba = gpu_display_rgba_half_.device_pointer;
|
||||
destination.d_pixels_half_rgba = display_rgba_half_.device_pointer;
|
||||
|
||||
get_render_tile_film_pixels(destination, pass_mode, num_samples);
|
||||
|
||||
queue_->copy_from_device(gpu_display_rgba_half_);
|
||||
queue_->copy_from_device(display_rgba_half_);
|
||||
queue_->synchronize();
|
||||
|
||||
gpu_display->copy_pixels_to_texture(
|
||||
gpu_display_rgba_half_.data(), texture_x, texture_y, width, height);
|
||||
display->copy_pixels_to_texture(display_rgba_half_.data(), texture_x, texture_y, width, height);
|
||||
}
|
||||
|
||||
bool PathTraceWorkGPU::copy_to_gpu_display_interop(GPUDisplay *gpu_display,
|
||||
PassMode pass_mode,
|
||||
int num_samples)
|
||||
bool PathTraceWorkGPU::copy_to_display_interop(PathTraceDisplay *display,
|
||||
PassMode pass_mode,
|
||||
int num_samples)
|
||||
{
|
||||
if (!device_graphics_interop_) {
|
||||
device_graphics_interop_ = queue_->graphics_interop_create();
|
||||
}
|
||||
|
||||
const DeviceGraphicsInteropDestination graphics_interop_dst =
|
||||
gpu_display->graphics_interop_get();
|
||||
device_graphics_interop_->set_destination(graphics_interop_dst);
|
||||
const DisplayDriver::GraphicsInterop graphics_interop_dst = display->graphics_interop_get();
|
||||
device_graphics_interop_->set_display_interop(graphics_interop_dst);
|
||||
|
||||
const device_ptr d_rgba_half = device_graphics_interop_->map();
|
||||
if (!d_rgba_half) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PassAccessor::Destination destination = get_gpu_display_destination_template(gpu_display);
|
||||
PassAccessor::Destination destination = get_display_destination_template(display);
|
||||
destination.d_pixels_half_rgba = d_rgba_half;
|
||||
|
||||
get_render_tile_film_pixels(destination, pass_mode, num_samples);
|
||||
@@ -772,14 +770,14 @@ bool PathTraceWorkGPU::copy_to_gpu_display_interop(GPUDisplay *gpu_display,
|
||||
return true;
|
||||
}
|
||||
|
||||
void PathTraceWorkGPU::destroy_gpu_resources(GPUDisplay *gpu_display)
|
||||
void PathTraceWorkGPU::destroy_gpu_resources(PathTraceDisplay *display)
|
||||
{
|
||||
if (!device_graphics_interop_) {
|
||||
return;
|
||||
}
|
||||
gpu_display->graphics_interop_activate();
|
||||
display->graphics_interop_activate();
|
||||
device_graphics_interop_ = nullptr;
|
||||
gpu_display->graphics_interop_deactivate();
|
||||
display->graphics_interop_deactivate();
|
||||
}
|
||||
|
||||
void PathTraceWorkGPU::get_render_tile_film_pixels(const PassAccessor::Destination &destination,
|
||||
|
@@ -48,10 +48,10 @@ class PathTraceWorkGPU : public PathTraceWork {
|
||||
int start_sample,
|
||||
int samples_num) override;
|
||||
|
||||
virtual void copy_to_gpu_display(GPUDisplay *gpu_display,
|
||||
PassMode pass_mode,
|
||||
int num_samples) override;
|
||||
virtual void destroy_gpu_resources(GPUDisplay *gpu_display) override;
|
||||
virtual void copy_to_display(PathTraceDisplay *display,
|
||||
PassMode pass_mode,
|
||||
int num_samples) override;
|
||||
virtual void destroy_gpu_resources(PathTraceDisplay *display) override;
|
||||
|
||||
virtual bool copy_render_buffers_from_device() override;
|
||||
virtual bool copy_render_buffers_to_device() override;
|
||||
@@ -88,16 +88,16 @@ class PathTraceWorkGPU : public PathTraceWork {
|
||||
|
||||
int get_num_active_paths();
|
||||
|
||||
/* Check whether graphics interop can be used for the GPUDisplay update. */
|
||||
/* Check whether graphics interop can be used for the PathTraceDisplay update. */
|
||||
bool should_use_graphics_interop();
|
||||
|
||||
/* Naive implementation of the `copy_to_gpu_display()` which performs film conversion on the
|
||||
* device, then copies pixels to the host and pushes them to the `gpu_display`. */
|
||||
void copy_to_gpu_display_naive(GPUDisplay *gpu_display, PassMode pass_mode, int num_samples);
|
||||
/* Naive implementation of the `copy_to_display()` which performs film conversion on the
|
||||
* device, then copies pixels to the host and pushes them to the `display`. */
|
||||
void copy_to_display_naive(PathTraceDisplay *display, PassMode pass_mode, int num_samples);
|
||||
|
||||
/* Implementation of `copy_to_gpu_display()` which uses driver's OpenGL/GPU interoperability
|
||||
/* Implementation of `copy_to_display()` which uses driver's OpenGL/GPU interoperability
|
||||
* functionality, avoiding copy of pixels to the host. */
|
||||
bool copy_to_gpu_display_interop(GPUDisplay *gpu_display, PassMode pass_mode, int num_samples);
|
||||
bool copy_to_display_interop(PathTraceDisplay *display, PassMode pass_mode, int num_samples);
|
||||
|
||||
/* Synchronously run film conversion kernel and store display result in the given destination. */
|
||||
void get_render_tile_film_pixels(const PassAccessor::Destination &destination,
|
||||
@@ -139,9 +139,9 @@ class PathTraceWorkGPU : public PathTraceWork {
|
||||
/* Temporary buffer for passing work tiles to kernel. */
|
||||
device_vector<KernelWorkTile> work_tiles_;
|
||||
|
||||
/* Temporary buffer used by the copy_to_gpu_display() whenever graphics interoperability is not
|
||||
/* Temporary buffer used by the copy_to_display() whenever graphics interoperability is not
|
||||
* available. Is allocated on-demand. */
|
||||
device_vector<half4> gpu_display_rgba_half_;
|
||||
device_vector<half4> display_rgba_half_;
|
||||
|
||||
unique_ptr<DeviceGraphicsInterop> device_graphics_interop_;
|
||||
|
||||
|
@@ -344,7 +344,7 @@ class RenderScheduler {
|
||||
/* Number of rendered samples on top of the start sample. */
|
||||
int num_rendered_samples = 0;
|
||||
|
||||
/* Point in time the latest GPUDisplay work has been scheduled. */
|
||||
/* Point in time the latest PathTraceDisplay work has been scheduled. */
|
||||
double last_display_update_time = 0.0;
|
||||
/* Value of -1 means display was never updated. */
|
||||
int last_display_update_sample = -1;
|
||||
|
@@ -186,8 +186,8 @@ ccl_device_inline float _shader_bsdf_multi_eval(const KernelGlobals *kg,
|
||||
float sum_sample_weight,
|
||||
const uint light_shader_flags)
|
||||
{
|
||||
/* this is the veach one-sample model with balance heuristic, some pdf
|
||||
* factors drop out when using balance heuristic weighting */
|
||||
/* This is the veach one-sample model with balance heuristic,
|
||||
* some PDF factors drop out when using balance heuristic weighting. */
|
||||
for (int i = 0; i < sd->num_closure; i++) {
|
||||
const ShaderClosure *sc = &sd->closure[i];
|
||||
|
||||
|
@@ -41,6 +41,7 @@ set(SRC_OSL
|
||||
node_vector_displacement.osl
|
||||
node_emission.osl
|
||||
node_environment_texture.osl
|
||||
node_float_curve.osl
|
||||
node_fresnel.osl
|
||||
node_gamma.osl
|
||||
node_geometry.osl
|
||||
|
32
intern/cycles/kernel/shaders/node_float_curve.osl
Normal file
32
intern/cycles/kernel/shaders/node_float_curve.osl
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2011-2021 Blender Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "node_ramp_util.h"
|
||||
#include "stdcycles.h"
|
||||
|
||||
shader node_float_curve(float ramp[] = {0.0},
|
||||
float min_x = 0.0,
|
||||
float max_x = 1.0,
|
||||
float ValueIn = 0.0,
|
||||
float Factor = 0.0,
|
||||
output float ValueOut = 0.0)
|
||||
{
|
||||
float c = (ValueIn - min_x) / (max_x - min_x);
|
||||
|
||||
ValueOut = rgb_ramp_lookup(ramp, c, 1, 1);
|
||||
|
||||
ValueOut = mix(ValueIn, ValueOut, Factor);
|
||||
}
|
@@ -493,11 +493,13 @@ ccl_device void svm_eval_nodes(INTEGRATOR_STATE_CONST_ARGS,
|
||||
case NODE_IES:
|
||||
svm_node_ies(kg, sd, stack, node);
|
||||
break;
|
||||
|
||||
case NODE_RGB_CURVES:
|
||||
case NODE_VECTOR_CURVES:
|
||||
offset = svm_node_curves(kg, sd, stack, node, offset);
|
||||
break;
|
||||
case NODE_FLOAT_CURVE:
|
||||
offset = svm_node_curve(kg, sd, stack, node, offset);
|
||||
break;
|
||||
case NODE_TANGENT:
|
||||
svm_node_tangent(kg, sd, stack, node);
|
||||
break;
|
||||
|
@@ -21,6 +21,48 @@ CCL_NAMESPACE_BEGIN
|
||||
|
||||
/* NOTE: svm_ramp.h, svm_ramp_util.h and node_ramp_util.h must stay consistent */
|
||||
|
||||
ccl_device_inline float fetch_float(const KernelGlobals *kg, int offset)
|
||||
{
|
||||
uint4 node = kernel_tex_fetch(__svm_nodes, offset);
|
||||
return __uint_as_float(node.x);
|
||||
}
|
||||
|
||||
ccl_device_inline float float_ramp_lookup(const KernelGlobals *kg,
|
||||
int offset,
|
||||
float f,
|
||||
bool interpolate,
|
||||
bool extrapolate,
|
||||
int table_size)
|
||||
{
|
||||
if ((f < 0.0f || f > 1.0f) && extrapolate) {
|
||||
float t0, dy;
|
||||
if (f < 0.0f) {
|
||||
t0 = fetch_float(kg, offset);
|
||||
dy = t0 - fetch_float(kg, offset + 1);
|
||||
f = -f;
|
||||
}
|
||||
else {
|
||||
t0 = fetch_float(kg, offset + table_size - 1);
|
||||
dy = t0 - fetch_float(kg, offset + table_size - 2);
|
||||
f = f - 1.0f;
|
||||
}
|
||||
return t0 + dy * f * (table_size - 1);
|
||||
}
|
||||
|
||||
f = saturate(f) * (table_size - 1);
|
||||
|
||||
/* clamp int as well in case of NaN */
|
||||
int i = clamp(float_to_int(f), 0, table_size - 1);
|
||||
float t = f - (float)i;
|
||||
|
||||
float a = fetch_float(kg, offset + i);
|
||||
|
||||
if (interpolate && t > 0.0f)
|
||||
a = (1.0f - t) * a + t * fetch_float(kg, offset + i + 1);
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
ccl_device_inline float4 rgb_ramp_lookup(const KernelGlobals *kg,
|
||||
int offset,
|
||||
float f,
|
||||
@@ -105,6 +147,30 @@ ccl_device_noinline int svm_node_curves(
|
||||
return offset;
|
||||
}
|
||||
|
||||
ccl_device_noinline int svm_node_curve(
|
||||
const KernelGlobals *kg, ShaderData *sd, float *stack, uint4 node, int offset)
|
||||
{
|
||||
uint fac_offset, value_in_offset, out_offset;
|
||||
svm_unpack_node_uchar3(node.y, &fac_offset, &value_in_offset, &out_offset);
|
||||
|
||||
uint table_size = read_node(kg, &offset).x;
|
||||
|
||||
float fac = stack_load_float(stack, fac_offset);
|
||||
float in = stack_load_float(stack, value_in_offset);
|
||||
|
||||
const float min = __int_as_float(node.z), max = __int_as_float(node.w);
|
||||
const float range = max - min;
|
||||
const float relpos = (in - min) / range;
|
||||
|
||||
float v = float_ramp_lookup(kg, offset, relpos, true, true, table_size);
|
||||
|
||||
in = (1.0f - fac) * in + fac * v;
|
||||
stack_store_float(stack, out_offset, in);
|
||||
|
||||
offset += table_size;
|
||||
return offset;
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
|
||||
#endif /* __SVM_RAMP_H__ */
|
||||
|
@@ -122,6 +122,7 @@ typedef enum ShaderNodeType {
|
||||
NODE_AOV_START,
|
||||
NODE_AOV_COLOR,
|
||||
NODE_AOV_VALUE,
|
||||
NODE_FLOAT_CURVE,
|
||||
/* NOTE: for best OpenCL performance, item definition in the enum must
|
||||
* match the switch case order in svm.h. */
|
||||
} ShaderNodeType;
|
||||
|
@@ -35,7 +35,6 @@ set(SRC
|
||||
denoising.cpp
|
||||
film.cpp
|
||||
geometry.cpp
|
||||
gpu_display.cpp
|
||||
graph.cpp
|
||||
hair.cpp
|
||||
image.cpp
|
||||
@@ -78,9 +77,10 @@ set(SRC_HEADERS
|
||||
colorspace.h
|
||||
constant_fold.h
|
||||
denoising.h
|
||||
display_driver.h
|
||||
output_driver.h
|
||||
film.h
|
||||
geometry.h
|
||||
gpu_display.h
|
||||
graph.h
|
||||
hair.h
|
||||
image.h
|
||||
|
131
intern/cycles/render/display_driver.h
Normal file
131
intern/cycles/render/display_driver.h
Normal file
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright 2021 Blender Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "util/util_half.h"
|
||||
#include "util/util_types.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
/* Display driver for efficient interactive display of renders.
|
||||
*
|
||||
* Host applications implement this interface for viewport rendering. For best performance, we
|
||||
* recommend:
|
||||
* - Allocating a texture on the GPU to be interactively updated
|
||||
* - Using the graphics interop mechanism to avoid CPU-GPU copying overhead
|
||||
* - Using a dedicated or thread-safe graphics API context for updates, to avoid
|
||||
* blocking the host application.
|
||||
*/
|
||||
class DisplayDriver {
|
||||
public:
|
||||
DisplayDriver() = default;
|
||||
virtual ~DisplayDriver() = default;
|
||||
|
||||
/* Render buffer parameters. */
|
||||
struct Params {
|
||||
public:
|
||||
/* Render resolution, ignoring progressive resolution changes.
|
||||
* The texture buffer should be allocated with this size. */
|
||||
int2 size = make_int2(0, 0);
|
||||
|
||||
/* For border rendering, the full resolution of the render, and the offset within that larger
|
||||
* render. */
|
||||
int2 full_size = make_int2(0, 0);
|
||||
int2 full_offset = make_int2(0, 0);
|
||||
|
||||
bool modified(const Params &other) const
|
||||
{
|
||||
return !(full_offset == other.full_offset && full_size == other.full_size &&
|
||||
size == other.size);
|
||||
}
|
||||
};
|
||||
|
||||
/* Update the render from the rendering thread.
|
||||
*
|
||||
* Cycles periodically updates the render to be displayed. For multithreaded updates with
|
||||
* potentially multiple rendering devices, it will call these methods as follows.
|
||||
*
|
||||
* if (driver.update_begin(params, width, height)) {
|
||||
* parallel_for_each(rendering_device) {
|
||||
* buffer = driver.map_texture_buffer();
|
||||
* if (buffer) {
|
||||
* fill(buffer);
|
||||
* driver.unmap_texture_buffer();
|
||||
* }
|
||||
* }
|
||||
* driver.update_end();
|
||||
* }
|
||||
*
|
||||
* The parameters may dynamically change due to camera changes in the scene, and resources should
|
||||
* be re-allocated accordingly.
|
||||
*
|
||||
* The width and height passed to update_begin() are the effective render resolution taking into
|
||||
* account progressive resolution changes, which may be equal to or smaller than the params.size.
|
||||
* For efficiency, changes in this resolution should be handled without re-allocating resources,
|
||||
* but rather by using a subset of the full resolution buffer. */
|
||||
virtual bool update_begin(const Params ¶ms, int width, int height) = 0;
|
||||
virtual void update_end() = 0;
|
||||
|
||||
virtual half4 *map_texture_buffer() = 0;
|
||||
virtual void unmap_texture_buffer() = 0;
|
||||
|
||||
/* Optionally return a handle to a native graphics API texture buffer. If supported,
|
||||
* the rendering device may write directly to this buffer instead of calling
|
||||
* map_texture_buffer() and unmap_texture_buffer(). */
|
||||
class GraphicsInterop {
|
||||
public:
|
||||
/* Dimensions of the buffer, in pixels. */
|
||||
int buffer_width = 0;
|
||||
int buffer_height = 0;
|
||||
|
||||
/* OpenGL pixel buffer object. */
|
||||
int opengl_pbo_id = 0;
|
||||
|
||||
/* Clear the entire buffer before doing partial write to it. */
|
||||
bool need_clear = false;
|
||||
};
|
||||
|
||||
virtual GraphicsInterop graphics_interop_get()
|
||||
{
|
||||
return GraphicsInterop();
|
||||
}
|
||||
|
||||
/* (De)activate graphics context required for editing or deleting the graphics interop
|
||||
* object.
|
||||
*
|
||||
* For example, destruction of the CUDA object associated with an OpenGL requires the
|
||||
* OpenGL context to be active. */
|
||||
virtual void graphics_interop_activate(){};
|
||||
virtual void graphics_interop_deactivate(){};
|
||||
|
||||
/* Clear the display buffer by filling it with zeros. */
|
||||
virtual void clear() = 0;
|
||||
|
||||
/* Draw the render using the native graphics API.
|
||||
*
|
||||
* Note that this may be called in parallel to updates. The implementation is responsible for
|
||||
* mutex locking or other mechanisms to avoid conflicts.
|
||||
*
|
||||
* The parameters may have changed since the last update. The implementation is responsible for
|
||||
* deciding to skip or adjust render display for such changes.
|
||||
*
|
||||
* Host application drawing the render buffer should use Session.draw(), which will
|
||||
* call this method. */
|
||||
virtual void draw(const Params ¶ms) = 0;
|
||||
};
|
||||
|
||||
CCL_NAMESPACE_END
|
@@ -6382,7 +6382,7 @@ void BumpNode::constant_fold(const ConstantFolder &folder)
|
||||
/* TODO(sergey): Ignore bump with zero strength. */
|
||||
}
|
||||
|
||||
/* Curve node */
|
||||
/* Curves node */
|
||||
|
||||
CurvesNode::CurvesNode(const NodeType *node_type) : ShaderNode(node_type)
|
||||
{
|
||||
@@ -6531,6 +6531,83 @@ void VectorCurvesNode::compile(OSLCompiler &compiler)
|
||||
CurvesNode::compile(compiler, "node_vector_curves");
|
||||
}
|
||||
|
||||
/* FloatCurveNode */
|
||||
|
||||
NODE_DEFINE(FloatCurveNode)
|
||||
{
|
||||
NodeType *type = NodeType::add("float_curve", create, NodeType::SHADER);
|
||||
|
||||
SOCKET_FLOAT_ARRAY(curve, "Curve", array<float>());
|
||||
SOCKET_FLOAT(min_x, "Min X", 0.0f);
|
||||
SOCKET_FLOAT(max_x, "Max X", 1.0f);
|
||||
|
||||
SOCKET_IN_FLOAT(fac, "Factor", 0.0f);
|
||||
SOCKET_IN_FLOAT(value, "Value", 0.0f);
|
||||
|
||||
SOCKET_OUT_FLOAT(value, "Value");
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
FloatCurveNode::FloatCurveNode() : ShaderNode(get_node_type())
|
||||
{
|
||||
}
|
||||
|
||||
void FloatCurveNode::constant_fold(const ConstantFolder &folder)
|
||||
{
|
||||
ShaderInput *value_in = input("Value");
|
||||
ShaderInput *fac_in = input("Factor");
|
||||
|
||||
/* evaluate fully constant node */
|
||||
if (folder.all_inputs_constant()) {
|
||||
if (curve.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
float pos = (value - min_x) / (max_x - min_x);
|
||||
float result = float_ramp_lookup(curve.data(), pos, true, true, curve.size());
|
||||
|
||||
folder.make_constant(value + fac * (result - value));
|
||||
}
|
||||
/* remove no-op node */
|
||||
else if (!fac_in->link && fac == 0.0f) {
|
||||
/* link is not null because otherwise all inputs are constant */
|
||||
folder.bypass(value_in->link);
|
||||
}
|
||||
}
|
||||
|
||||
void FloatCurveNode::compile(SVMCompiler &compiler)
|
||||
{
|
||||
if (curve.size() == 0)
|
||||
return;
|
||||
|
||||
ShaderInput *value_in = input("Value");
|
||||
ShaderInput *fac_in = input("Factor");
|
||||
ShaderOutput *value_out = output("Value");
|
||||
|
||||
compiler.add_node(NODE_FLOAT_CURVE,
|
||||
compiler.encode_uchar4(compiler.stack_assign(fac_in),
|
||||
compiler.stack_assign(value_in),
|
||||
compiler.stack_assign(value_out)),
|
||||
__float_as_int(min_x),
|
||||
__float_as_int(max_x));
|
||||
|
||||
compiler.add_node(curve.size());
|
||||
for (int i = 0; i < curve.size(); i++)
|
||||
compiler.add_node(make_float4(curve[i]));
|
||||
}
|
||||
|
||||
void FloatCurveNode::compile(OSLCompiler &compiler)
|
||||
{
|
||||
if (curve.size() == 0)
|
||||
return;
|
||||
|
||||
compiler.parameter_array("ramp", curve.data(), curve.size());
|
||||
compiler.parameter(this, "min_x");
|
||||
compiler.parameter(this, "max_x");
|
||||
compiler.add(this, "node_float_curve");
|
||||
}
|
||||
|
||||
/* RGBRampNode */
|
||||
|
||||
NODE_DEFINE(RGBRampNode)
|
||||
|
@@ -1398,6 +1398,18 @@ class VectorCurvesNode : public CurvesNode {
|
||||
void constant_fold(const ConstantFolder &folder);
|
||||
};
|
||||
|
||||
class FloatCurveNode : public ShaderNode {
|
||||
public:
|
||||
SHADER_NODE_CLASS(FloatCurveNode)
|
||||
void constant_fold(const ConstantFolder &folder);
|
||||
|
||||
NODE_SOCKET_API_ARRAY(array<float>, curve)
|
||||
NODE_SOCKET_API(float, min_x)
|
||||
NODE_SOCKET_API(float, max_x)
|
||||
NODE_SOCKET_API(float, fac)
|
||||
NODE_SOCKET_API(float, value)
|
||||
};
|
||||
|
||||
class RGBRampNode : public ShaderNode {
|
||||
public:
|
||||
SHADER_NODE_CLASS(RGBRampNode)
|
||||
|
82
intern/cycles/render/output_driver.h
Normal file
82
intern/cycles/render/output_driver.h
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2021 Blender Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "util/util_math.h"
|
||||
#include "util/util_string.h"
|
||||
#include "util/util_types.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
/* Output driver for reading render buffers.
|
||||
*
|
||||
* Host applications implement this interface for outputting render buffers for offline rendering.
|
||||
* Drivers can be used to copy the buffers into the host application or write them directly to
|
||||
* disk. This interface may also be used for interactive display, however the DisplayDriver is more
|
||||
* efficient for that purpose.
|
||||
*/
|
||||
class OutputDriver {
|
||||
public:
|
||||
OutputDriver() = default;
|
||||
virtual ~OutputDriver() = default;
|
||||
|
||||
class Tile {
|
||||
public:
|
||||
Tile(const int2 offset,
|
||||
const int2 size,
|
||||
const int2 full_size,
|
||||
const string_view layer,
|
||||
const string_view view)
|
||||
: offset(offset), size(size), full_size(full_size), layer(layer), view(view)
|
||||
{
|
||||
}
|
||||
virtual ~Tile() = default;
|
||||
|
||||
const int2 offset;
|
||||
const int2 size;
|
||||
const int2 full_size;
|
||||
const string layer;
|
||||
const string view;
|
||||
|
||||
virtual bool get_pass_pixels(const string_view pass_name,
|
||||
const int num_channels,
|
||||
float *pixels) const = 0;
|
||||
virtual bool set_pass_pixels(const string_view pass_name,
|
||||
const int num_channels,
|
||||
const float *pixels) const = 0;
|
||||
};
|
||||
|
||||
/* Write tile once it has finished rendering. */
|
||||
virtual void write_render_tile(const Tile &tile) = 0;
|
||||
|
||||
/* Update tile while rendering is in progress. Return true if any update
|
||||
* was performed. */
|
||||
virtual bool update_render_tile(const Tile & /* tile */)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/* For baking, read render pass PASS_BAKE_PRIMITIVE and PASS_BAKE_DIFFERENTIAL
|
||||
* to determine which shading points to use for baking at each pixel. Return
|
||||
* true if any data was read. */
|
||||
virtual bool read_render_tile(const Tile & /* tile */)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
CCL_NAMESPACE_END
|
@@ -25,12 +25,13 @@
|
||||
#include "render/bake.h"
|
||||
#include "render/buffers.h"
|
||||
#include "render/camera.h"
|
||||
#include "render/gpu_display.h"
|
||||
#include "render/display_driver.h"
|
||||
#include "render/graph.h"
|
||||
#include "render/integrator.h"
|
||||
#include "render/light.h"
|
||||
#include "render/mesh.h"
|
||||
#include "render/object.h"
|
||||
#include "render/output_driver.h"
|
||||
#include "render/scene.h"
|
||||
#include "render/session.h"
|
||||
|
||||
@@ -64,25 +65,6 @@ Session::Session(const SessionParams ¶ms_, const SceneParams &scene_params)
|
||||
path_trace_ = make_unique<PathTrace>(
|
||||
device, scene->film, &scene->dscene, render_scheduler_, tile_manager_);
|
||||
path_trace_->set_progress(&progress);
|
||||
path_trace_->tile_buffer_update_cb = [&]() {
|
||||
if (!update_render_tile_cb) {
|
||||
return;
|
||||
}
|
||||
update_render_tile_cb();
|
||||
};
|
||||
path_trace_->tile_buffer_write_cb = [&]() {
|
||||
if (!write_render_tile_cb) {
|
||||
return;
|
||||
}
|
||||
write_render_tile_cb();
|
||||
};
|
||||
path_trace_->tile_buffer_read_cb = [&]() -> bool {
|
||||
if (!read_render_tile_cb) {
|
||||
return false;
|
||||
}
|
||||
read_render_tile_cb();
|
||||
return true;
|
||||
};
|
||||
path_trace_->progress_update_cb = [&]() { update_status_time(); };
|
||||
|
||||
tile_manager_.full_buffer_written_cb = [&](string_view filename) {
|
||||
@@ -97,24 +79,6 @@ Session::~Session()
|
||||
{
|
||||
cancel();
|
||||
|
||||
/* TODO(sergey): Bring the passes in viewport back.
|
||||
* It is unclear why there is such an exception needed though. */
|
||||
#if 0
|
||||
if (buffers && params.write_render_cb) {
|
||||
/* Copy to display buffer and write out image if requested */
|
||||
delete display;
|
||||
|
||||
display = new DisplayBuffer(device, false);
|
||||
display->reset(buffers->params);
|
||||
copy_to_display_buffer(params.samples);
|
||||
|
||||
int w = display->draw_width;
|
||||
int h = display->draw_height;
|
||||
uchar4 *pixels = display->rgba_byte.copy_from_device(0, w, h);
|
||||
params.write_render_cb((uchar *)pixels, w, h, 4);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Make sure path tracer is destroyed before the device. This is needed because destruction might
|
||||
* need to access device for device memory free. */
|
||||
/* TODO(sergey): Convert device to be unique_ptr, and rely on C++ to destruct objects in the
|
||||
@@ -162,7 +126,7 @@ bool Session::ready_to_reset()
|
||||
|
||||
void Session::run_main_render_loop()
|
||||
{
|
||||
path_trace_->clear_gpu_display();
|
||||
path_trace_->clear_display();
|
||||
|
||||
while (true) {
|
||||
RenderWork render_work = run_update_for_next_iteration();
|
||||
@@ -514,9 +478,14 @@ void Session::set_pause(bool pause)
|
||||
}
|
||||
}
|
||||
|
||||
void Session::set_gpu_display(unique_ptr<GPUDisplay> gpu_display)
|
||||
void Session::set_output_driver(unique_ptr<OutputDriver> driver)
|
||||
{
|
||||
path_trace_->set_gpu_display(move(gpu_display));
|
||||
path_trace_->set_output_driver(move(driver));
|
||||
}
|
||||
|
||||
void Session::set_display_driver(unique_ptr<DisplayDriver> driver)
|
||||
{
|
||||
path_trace_->set_display_driver(move(driver));
|
||||
}
|
||||
|
||||
double Session::get_estimated_remaining_time() const
|
||||
@@ -636,101 +605,6 @@ void Session::collect_statistics(RenderStats *render_stats)
|
||||
}
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* Tile and tile pixels access.
|
||||
*/
|
||||
|
||||
bool Session::has_multiple_render_tiles() const
|
||||
{
|
||||
return tile_manager_.has_multiple_tiles();
|
||||
}
|
||||
|
||||
int2 Session::get_render_tile_size() const
|
||||
{
|
||||
return path_trace_->get_render_tile_size();
|
||||
}
|
||||
|
||||
int2 Session::get_render_tile_offset() const
|
||||
{
|
||||
return path_trace_->get_render_tile_offset();
|
||||
}
|
||||
|
||||
string_view Session::get_render_tile_layer() const
|
||||
{
|
||||
const BufferParams &buffer_params = path_trace_->get_render_tile_params();
|
||||
return buffer_params.layer;
|
||||
}
|
||||
|
||||
string_view Session::get_render_tile_view() const
|
||||
{
|
||||
const BufferParams &buffer_params = path_trace_->get_render_tile_params();
|
||||
return buffer_params.view;
|
||||
}
|
||||
|
||||
bool Session::copy_render_tile_from_device()
|
||||
{
|
||||
return path_trace_->copy_render_tile_from_device();
|
||||
}
|
||||
|
||||
bool Session::get_render_tile_pixels(const string &pass_name, int num_components, float *pixels)
|
||||
{
|
||||
/* NOTE: The code relies on a fact that session is fully update and no scene/buffer modification
|
||||
* is happening while this function runs. */
|
||||
|
||||
const BufferParams &buffer_params = path_trace_->get_render_tile_params();
|
||||
|
||||
const BufferPass *pass = buffer_params.find_pass(pass_name);
|
||||
if (pass == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool has_denoised_result = path_trace_->has_denoised_result();
|
||||
if (pass->mode == PassMode::DENOISED && !has_denoised_result) {
|
||||
pass = buffer_params.find_pass(pass->type);
|
||||
if (pass == nullptr) {
|
||||
/* Happens when denoised result pass is requested but is never written by the kernel. */
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
pass = buffer_params.get_actual_display_pass(pass);
|
||||
|
||||
const float exposure = buffer_params.exposure;
|
||||
const int num_samples = path_trace_->get_num_render_tile_samples();
|
||||
|
||||
PassAccessor::PassAccessInfo pass_access_info(*pass);
|
||||
pass_access_info.use_approximate_shadow_catcher = buffer_params.use_approximate_shadow_catcher;
|
||||
pass_access_info.use_approximate_shadow_catcher_background =
|
||||
pass_access_info.use_approximate_shadow_catcher && !buffer_params.use_transparent_background;
|
||||
|
||||
const PassAccessorCPU pass_accessor(pass_access_info, exposure, num_samples);
|
||||
const PassAccessor::Destination destination(pixels, num_components);
|
||||
|
||||
return path_trace_->get_render_tile_pixels(pass_accessor, destination);
|
||||
}
|
||||
|
||||
bool Session::set_render_tile_pixels(const string &pass_name,
|
||||
int num_components,
|
||||
const float *pixels)
|
||||
{
|
||||
/* NOTE: The code relies on a fact that session is fully update and no scene/buffer modification
|
||||
* is happening while this function runs. */
|
||||
|
||||
const BufferPass *pass = buffer_params_.find_pass(pass_name);
|
||||
if (!pass) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const float exposure = scene->film->get_exposure();
|
||||
const int num_samples = render_scheduler_.get_num_rendered_samples();
|
||||
|
||||
const PassAccessor::PassAccessInfo pass_access_info(*pass);
|
||||
PassAccessorCPU pass_accessor(pass_access_info, exposure, num_samples);
|
||||
PassAccessor::Source source(pixels, num_components);
|
||||
|
||||
return path_trace_->set_render_tile_pixels(pass_accessor, source);
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* Full-frame on-disk storage.
|
||||
*/
|
||||
|
@@ -35,9 +35,10 @@ CCL_NAMESPACE_BEGIN
|
||||
class BufferParams;
|
||||
class Device;
|
||||
class DeviceScene;
|
||||
class DisplayDriver;
|
||||
class OutputDriver;
|
||||
class PathTrace;
|
||||
class Progress;
|
||||
class GPUDisplay;
|
||||
class RenderBuffers;
|
||||
class Scene;
|
||||
class SceneParams;
|
||||
@@ -67,8 +68,6 @@ class SessionParams {
|
||||
|
||||
ShadingSystem shadingsystem;
|
||||
|
||||
function<bool(const uchar *pixels, int width, int height, int channels)> write_render_cb;
|
||||
|
||||
SessionParams()
|
||||
{
|
||||
headless = false;
|
||||
@@ -114,10 +113,6 @@ class Session {
|
||||
Stats stats;
|
||||
Profiler profiler;
|
||||
|
||||
function<void(void)> write_render_tile_cb;
|
||||
function<void(void)> update_render_tile_cb;
|
||||
function<void(void)> read_render_tile_cb;
|
||||
|
||||
/* Callback is invoked by tile manager whenever on-dist tiles storage file is closed after
|
||||
* writing. Allows an engine integration to keep track of those files without worry about
|
||||
* transferring the information when it needs to re-create session during rendering. */
|
||||
@@ -143,7 +138,8 @@ class Session {
|
||||
void set_samples(int samples);
|
||||
void set_time_limit(double time_limit);
|
||||
|
||||
void set_gpu_display(unique_ptr<GPUDisplay> gpu_display);
|
||||
void set_output_driver(unique_ptr<OutputDriver> driver);
|
||||
void set_display_driver(unique_ptr<DisplayDriver> driver);
|
||||
|
||||
double get_estimated_remaining_time() const;
|
||||
|
||||
@@ -155,24 +151,6 @@ class Session {
|
||||
|
||||
void collect_statistics(RenderStats *stats);
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* Tile and tile pixels access.
|
||||
*/
|
||||
|
||||
bool has_multiple_render_tiles() const;
|
||||
|
||||
/* Get size and offset (relative to the buffer's full x/y) of the currently rendering tile. */
|
||||
int2 get_render_tile_size() const;
|
||||
int2 get_render_tile_offset() const;
|
||||
|
||||
string_view get_render_tile_layer() const;
|
||||
string_view get_render_tile_view() const;
|
||||
|
||||
bool copy_render_tile_from_device();
|
||||
|
||||
bool get_render_tile_pixels(const string &pass_name, int num_components, float *pixels);
|
||||
bool set_render_tile_pixels(const string &pass_name, int num_components, const float *pixels);
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* Full-frame on-disk storage.
|
||||
*/
|
||||
|
@@ -420,6 +420,11 @@ const Tile &TileManager::get_current_tile() const
|
||||
return tile_state_.current_tile;
|
||||
}
|
||||
|
||||
const int2 TileManager::get_size() const
|
||||
{
|
||||
return make_int2(buffer_params_.width, buffer_params_.height);
|
||||
}
|
||||
|
||||
bool TileManager::open_tile_output()
|
||||
{
|
||||
write_state_.filename = path_temp_get("cycles-tile-buffer-" + tile_file_unique_part_ + "-" +
|
||||
|
@@ -82,6 +82,7 @@ class TileManager {
|
||||
bool done();
|
||||
|
||||
const Tile &get_current_tile() const;
|
||||
const int2 get_size() const;
|
||||
|
||||
/* Write render buffer of a tile to a file on disk.
|
||||
*
|
||||
|
@@ -473,6 +473,7 @@ if(WITH_XR_OPENXR)
|
||||
intern/GHOST_Xr.cpp
|
||||
intern/GHOST_XrAction.cpp
|
||||
intern/GHOST_XrContext.cpp
|
||||
intern/GHOST_XrControllerModel.cpp
|
||||
intern/GHOST_XrEvent.cpp
|
||||
intern/GHOST_XrGraphicsBinding.cpp
|
||||
intern/GHOST_XrSession.cpp
|
||||
@@ -482,13 +483,19 @@ if(WITH_XR_OPENXR)
|
||||
intern/GHOST_IXrGraphicsBinding.h
|
||||
intern/GHOST_XrAction.h
|
||||
intern/GHOST_XrContext.h
|
||||
intern/GHOST_XrControllerModel.h
|
||||
intern/GHOST_XrException.h
|
||||
intern/GHOST_XrSession.h
|
||||
intern/GHOST_XrSwapchain.h
|
||||
intern/GHOST_Xr_intern.h
|
||||
intern/GHOST_Xr_openxr_includes.h
|
||||
)
|
||||
list(APPEND INC
|
||||
../../extern/json/include
|
||||
../../extern/tinygltf
|
||||
)
|
||||
list(APPEND INC_SYS
|
||||
${EIGEN3_INCLUDE_DIRS}
|
||||
${XR_OPENXR_SDK_INCLUDE_DIR}
|
||||
)
|
||||
list(APPEND LIB
|
||||
|
@@ -1140,6 +1140,30 @@ void GHOST_XrGetActionCustomdataArray(GHOST_XrContextHandle xr_context,
|
||||
const char *action_set_name,
|
||||
void **r_customdata_array);
|
||||
|
||||
/* controller model */
|
||||
/**
|
||||
* Load the OpenXR controller model.
|
||||
*/
|
||||
int GHOST_XrLoadControllerModel(GHOST_XrContextHandle xr_context, const char *subaction_path);
|
||||
|
||||
/**
|
||||
* Unload the OpenXR controller model.
|
||||
*/
|
||||
void GHOST_XrUnloadControllerModel(GHOST_XrContextHandle xr_context, const char *subaction_path);
|
||||
|
||||
/**
|
||||
* Update component transforms for the OpenXR controller model.
|
||||
*/
|
||||
int GHOST_XrUpdateControllerModelComponents(GHOST_XrContextHandle xr_context,
|
||||
const char *subaction_path);
|
||||
|
||||
/**
|
||||
* Get vertex data for the OpenXR controller model.
|
||||
*/
|
||||
int GHOST_XrGetControllerModelData(GHOST_XrContextHandle xr_context,
|
||||
const char *subaction_path,
|
||||
GHOST_XrControllerModelData *r_data);
|
||||
|
||||
#endif /* WITH_XR_OPENXR */
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
@@ -643,7 +643,7 @@ typedef void (*GHOST_XrDrawViewFn)(const struct GHOST_XrDrawViewInfo *draw_view,
|
||||
*/
|
||||
typedef const GHOST_TXrGraphicsBinding *GHOST_XrGraphicsBindingCandidates;
|
||||
|
||||
typedef struct {
|
||||
typedef struct GHOST_XrPose {
|
||||
float position[3];
|
||||
/* Blender convention (w, x, y, z) */
|
||||
float orientation_quat[4];
|
||||
@@ -753,8 +753,31 @@ typedef struct GHOST_XrActionProfileInfo {
|
||||
const char *profile_path;
|
||||
uint32_t count_subaction_paths;
|
||||
const char **subaction_paths;
|
||||
/* Bindings for each subaction path. */
|
||||
/** Bindings for each subaction path. */
|
||||
const GHOST_XrActionBindingInfo *bindings;
|
||||
} GHOST_XrActionProfileInfo;
|
||||
|
||||
typedef struct GHOST_XrControllerModelVertex {
|
||||
float position[3];
|
||||
float normal[3];
|
||||
} GHOST_XrControllerModelVertex;
|
||||
|
||||
typedef struct GHOST_XrControllerModelComponent {
|
||||
/** World space transform. */
|
||||
float transform[4][4];
|
||||
uint32_t vertex_offset;
|
||||
uint32_t vertex_count;
|
||||
uint32_t index_offset;
|
||||
uint32_t index_count;
|
||||
} GHOST_XrControllerModelComponent;
|
||||
|
||||
typedef struct GHOST_XrControllerModelData {
|
||||
uint32_t count_vertices;
|
||||
const GHOST_XrControllerModelVertex *vertices;
|
||||
uint32_t count_indices;
|
||||
const uint32_t *indices;
|
||||
uint32_t count_components;
|
||||
const GHOST_XrControllerModelComponent *components;
|
||||
} GHOST_XrControllerModelData;
|
||||
|
||||
#endif /* WITH_XR_OPENXR */
|
||||
|
@@ -1069,4 +1069,39 @@ void GHOST_XrGetActionCustomdataArray(GHOST_XrContextHandle xr_contexthandle,
|
||||
xr_context);
|
||||
}
|
||||
|
||||
int GHOST_XrLoadControllerModel(GHOST_XrContextHandle xr_contexthandle, const char *subaction_path)
|
||||
{
|
||||
GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle;
|
||||
GHOST_XrSession *xr_session = xr_context->getSession();
|
||||
GHOST_XR_CAPI_CALL_RET(xr_session->loadControllerModel(subaction_path), xr_context);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void GHOST_XrUnloadControllerModel(GHOST_XrContextHandle xr_contexthandle,
|
||||
const char *subaction_path)
|
||||
{
|
||||
GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle;
|
||||
GHOST_XrSession *xr_session = xr_context->getSession();
|
||||
GHOST_XR_CAPI_CALL(xr_session->unloadControllerModel(subaction_path), xr_context);
|
||||
}
|
||||
|
||||
int GHOST_XrUpdateControllerModelComponents(GHOST_XrContextHandle xr_contexthandle,
|
||||
const char *subaction_path)
|
||||
{
|
||||
GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle;
|
||||
GHOST_XrSession *xr_session = xr_context->getSession();
|
||||
GHOST_XR_CAPI_CALL_RET(xr_session->updateControllerModelComponents(subaction_path), xr_context);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int GHOST_XrGetControllerModelData(GHOST_XrContextHandle xr_contexthandle,
|
||||
const char *subaction_path,
|
||||
GHOST_XrControllerModelData *r_data)
|
||||
{
|
||||
GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle;
|
||||
GHOST_XrSession *xr_session = xr_context->getSession();
|
||||
GHOST_XR_CAPI_CALL_RET(xr_session->getControllerModelData(subaction_path, *r_data), xr_context);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* WITH_XR_OPENXR */
|
||||
|
@@ -101,8 +101,7 @@ GHOST_TSuccess GHOST_DisplayManagerSDL::setCurrentDisplaySetting(
|
||||
uint8_t display, const GHOST_DisplaySetting &setting)
|
||||
{
|
||||
/*
|
||||
* Mode switching code ported from Quake 2 version 3.21 and bzflag version
|
||||
* 2.4.0:
|
||||
* Mode switching code ported from Quake 2 version 3.21 and BZFLAG version 2.4.0:
|
||||
* ftp://ftp.idsoftware.com/idstuff/source/q2source-3.21.zip
|
||||
* See linux/gl_glx.c:GLimp_SetMode
|
||||
* http://wiki.bzflag.org/BZFlag_Source
|
||||
|
@@ -1528,13 +1528,13 @@ void GHOST_SystemX11::processEvent(XEvent *xe)
|
||||
window->GetTabletData().Pressure = axis_value / ((float)xtablet.PressureLevels);
|
||||
}
|
||||
|
||||
/* the (short) cast and the & 0xffff is bizarre and unexplained anywhere,
|
||||
* but I got garbage data without it. Found it in the xidump.c source --matt
|
||||
/* NOTE(@broken): the (short) cast and the & 0xffff is bizarre and unexplained anywhere,
|
||||
* but I got garbage data without it. Found it in the `xidump.c` source.
|
||||
*
|
||||
* The '& 0xffff' just truncates the value to its two lowest bytes, this probably means
|
||||
* some drivers do not properly set the whole int value? Since we convert to float
|
||||
* afterward, I don't think we need to cast to short here, but do not have a device to
|
||||
* check this. --mont29
|
||||
* NOTE(@mont29): The '& 0xffff' just truncates the value to its two lowest bytes,
|
||||
* this probably means some drivers do not properly set the whole int value?
|
||||
* Since we convert to float afterward,
|
||||
* I don't think we need to cast to short here, but do not have a device to check this.
|
||||
*/
|
||||
if (AXIS_VALUE_GET(3, axis_value)) {
|
||||
window->GetTabletData().Xtilt = (short)(axis_value & 0xffff) /
|
||||
|
@@ -1092,9 +1092,9 @@ GHOST_TSuccess GHOST_WindowX11::setOrder(GHOST_TWindowOrder order)
|
||||
XWindowAttributes attr;
|
||||
Atom atom;
|
||||
|
||||
/* We use both XRaiseWindow and _NET_ACTIVE_WINDOW, since some
|
||||
* window managers ignore the former (e.g. kwin from kde) and others
|
||||
* don't implement the latter (e.g. fluxbox pre 0.9.9) */
|
||||
/* We use both #XRaiseWindow and #_NET_ACTIVE_WINDOW, since some
|
||||
* window managers ignore the former (e.g. KWIN from KDE) and others
|
||||
* don't implement the latter (e.g. FLUXBOX before 0.9.9). */
|
||||
|
||||
XRaiseWindow(m_display, m_window);
|
||||
|
||||
|
@@ -22,7 +22,6 @@
|
||||
#include <cstring>
|
||||
|
||||
#include "GHOST_Types.h"
|
||||
|
||||
#include "GHOST_XrException.h"
|
||||
#include "GHOST_Xr_intern.h"
|
||||
|
||||
|
@@ -26,6 +26,7 @@
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "GHOST_Util.h"
|
||||
|
||||
|
@@ -412,11 +412,14 @@ void GHOST_XrContext::getExtensionsToEnable(
|
||||
try_ext.push_back(XR_EXT_DEBUG_UTILS_EXTENSION_NAME);
|
||||
}
|
||||
|
||||
/* Try enabling interaction profile extensions. */
|
||||
/* Interaction profile extensions. */
|
||||
try_ext.push_back(XR_EXT_HP_MIXED_REALITY_CONTROLLER_EXTENSION_NAME);
|
||||
try_ext.push_back(XR_HTC_VIVE_COSMOS_CONTROLLER_INTERACTION_EXTENSION_NAME);
|
||||
try_ext.push_back(XR_HUAWEI_CONTROLLER_INTERACTION_EXTENSION_NAME);
|
||||
|
||||
/* Controller model extension. */
|
||||
try_ext.push_back(XR_MSFT_CONTROLLER_MODEL_EXTENSION_NAME);
|
||||
|
||||
/* Varjo quad view extension. */
|
||||
try_ext.push_back(XR_VARJO_QUAD_VIEWS_EXTENSION_NAME);
|
||||
|
||||
|
629
intern/ghost/intern/GHOST_XrControllerModel.cpp
Normal file
629
intern/ghost/intern/GHOST_XrControllerModel.cpp
Normal file
@@ -0,0 +1,629 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/** \file
|
||||
* \ingroup GHOST
|
||||
*/
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include <Eigen/Core>
|
||||
#include <Eigen/Geometry>
|
||||
|
||||
#include "GHOST_Types.h"
|
||||
#include "GHOST_XrException.h"
|
||||
#include "GHOST_Xr_intern.h"
|
||||
|
||||
#include "GHOST_XrControllerModel.h"
|
||||
|
||||
#define TINYGLTF_IMPLEMENTATION
|
||||
#define TINYGLTF_NO_STB_IMAGE
|
||||
#define TINYGLTF_NO_STB_IMAGE_WRITE
|
||||
#define STBIWDEF static inline
|
||||
#include "tiny_gltf.h"
|
||||
|
||||
struct GHOST_XrControllerModelNode {
|
||||
int32_t parent_idx = -1;
|
||||
int32_t component_idx = -1;
|
||||
float local_transform[4][4];
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name glTF Utilities
|
||||
*
|
||||
* Adapted from Microsoft OpenXR-Mixed Reality Samples (MIT License):
|
||||
* https://github.com/microsoft/OpenXR-MixedReality
|
||||
* \{ */
|
||||
|
||||
struct GHOST_XrPrimitive {
|
||||
std::vector<GHOST_XrControllerModelVertex> vertices;
|
||||
std::vector<uint32_t> indices;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate that an accessor does not go out of bounds of the buffer view that it references and
|
||||
* that the buffer view does not exceed the bounds of the buffer that it references
|
||||
*/
|
||||
static void validate_accessor(const tinygltf::Accessor &accessor,
|
||||
const tinygltf::BufferView &buffer_view,
|
||||
const tinygltf::Buffer &buffer,
|
||||
size_t byte_stride,
|
||||
size_t element_size)
|
||||
{
|
||||
/* Make sure the accessor does not go out of range of the buffer view. */
|
||||
if (accessor.byteOffset + (accessor.count - 1) * byte_stride + element_size >
|
||||
buffer_view.byteLength) {
|
||||
throw GHOST_XrException("glTF: Accessor goes out of range of bufferview.");
|
||||
}
|
||||
|
||||
/* Make sure the buffer view does not go out of range of the buffer. */
|
||||
if (buffer_view.byteOffset + buffer_view.byteLength > buffer.data.size()) {
|
||||
throw GHOST_XrException("glTF: BufferView goes out of range of buffer.");
|
||||
}
|
||||
}
|
||||
|
||||
template<float (GHOST_XrControllerModelVertex::*field)[3]>
|
||||
static void read_vertices(const tinygltf::Accessor &accessor,
|
||||
const tinygltf::BufferView &buffer_view,
|
||||
const tinygltf::Buffer &buffer,
|
||||
GHOST_XrPrimitive &primitive)
|
||||
{
|
||||
if (accessor.type != TINYGLTF_TYPE_VEC3) {
|
||||
throw GHOST_XrException(
|
||||
"glTF: Accessor for primitive attribute has incorrect type (VEC3 expected).");
|
||||
}
|
||||
|
||||
if (accessor.componentType != TINYGLTF_COMPONENT_TYPE_FLOAT) {
|
||||
throw GHOST_XrException(
|
||||
"glTF: Accessor for primitive attribute has incorrect component type (FLOAT expected).");
|
||||
}
|
||||
|
||||
/* If stride is not specified, it is tightly packed. */
|
||||
constexpr size_t packed_size = sizeof(float) * 3;
|
||||
const size_t stride = buffer_view.byteStride == 0 ? packed_size : buffer_view.byteStride;
|
||||
validate_accessor(accessor, buffer_view, buffer, stride, packed_size);
|
||||
|
||||
/* Resize the vertices vector, if necessary, to include room for the attribute data.
|
||||
If there are multiple attributes for a primitive, the first one will resize, and the
|
||||
subsequent will not need to. */
|
||||
primitive.vertices.resize(accessor.count);
|
||||
|
||||
/* Copy the attribute value over from the glTF buffer into the appropriate vertex field. */
|
||||
const uint8_t *buffer_ptr = buffer.data.data() + buffer_view.byteOffset + accessor.byteOffset;
|
||||
for (size_t i = 0; i < accessor.count; i++, buffer_ptr += stride) {
|
||||
memcpy(primitive.vertices[i].*field, buffer_ptr, stride);
|
||||
}
|
||||
}
|
||||
|
||||
static void load_attribute_accessor(const tinygltf::Model &gltf_model,
|
||||
const std::string &attribute_name,
|
||||
int accessor_id,
|
||||
GHOST_XrPrimitive &primitive)
|
||||
{
|
||||
const auto &accessor = gltf_model.accessors.at(accessor_id);
|
||||
|
||||
if (accessor.bufferView == -1) {
|
||||
throw GHOST_XrException("glTF: Accessor for primitive attribute specifies no bufferview.");
|
||||
}
|
||||
|
||||
const tinygltf::BufferView &buffer_view = gltf_model.bufferViews.at(accessor.bufferView);
|
||||
if (buffer_view.target != TINYGLTF_TARGET_ARRAY_BUFFER && buffer_view.target != 0) {
|
||||
throw GHOST_XrException(
|
||||
"glTF: Accessor for primitive attribute uses bufferview with invalid 'target' type.");
|
||||
}
|
||||
|
||||
const tinygltf::Buffer &buffer = gltf_model.buffers.at(buffer_view.buffer);
|
||||
|
||||
if (attribute_name.compare("POSITION") == 0) {
|
||||
read_vertices<&GHOST_XrControllerModelVertex::position>(
|
||||
accessor, buffer_view, buffer, primitive);
|
||||
}
|
||||
else if (attribute_name.compare("NORMAL") == 0) {
|
||||
read_vertices<&GHOST_XrControllerModelVertex::normal>(
|
||||
accessor, buffer_view, buffer, primitive);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads index data from a glTF primitive into a GHOST_XrPrimitive. glTF indices may be 8bit, 16bit
|
||||
* or 32bit integers. This will coalesce indices from the source type(s) into a 32bit integer.
|
||||
*/
|
||||
template<typename TSrcIndex>
|
||||
static void read_indices(const tinygltf::Accessor &accessor,
|
||||
const tinygltf::BufferView &buffer_view,
|
||||
const tinygltf::Buffer &buffer,
|
||||
GHOST_XrPrimitive &primitive)
|
||||
{
|
||||
if (buffer_view.target != TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER &&
|
||||
buffer_view.target != 0) { /* Allow 0 (not specified) even though spec doesn't seem to allow
|
||||
this (BoomBox GLB fails). */
|
||||
throw GHOST_XrException(
|
||||
"glTF: Accessor for indices uses bufferview with invalid 'target' type.");
|
||||
}
|
||||
|
||||
constexpr size_t component_size_bytes = sizeof(TSrcIndex);
|
||||
if (buffer_view.byteStride != 0 &&
|
||||
buffer_view.byteStride !=
|
||||
component_size_bytes) { /* Index buffer must be packed per glTF spec. */
|
||||
throw GHOST_XrException(
|
||||
"glTF: Accessor for indices uses bufferview with invalid 'byteStride'.");
|
||||
}
|
||||
|
||||
validate_accessor(accessor, buffer_view, buffer, component_size_bytes, component_size_bytes);
|
||||
|
||||
if ((accessor.count % 3) != 0) { /* Since only triangles are supported, enforce that the number
|
||||
of indices is divisible by 3. */
|
||||
throw GHOST_XrException("glTF: Unexpected number of indices for triangle primitive");
|
||||
}
|
||||
|
||||
const TSrcIndex *index_buffer = reinterpret_cast<const TSrcIndex *>(
|
||||
buffer.data.data() + buffer_view.byteOffset + accessor.byteOffset);
|
||||
for (uint32_t i = 0; i < accessor.count; i++) {
|
||||
primitive.indices.push_back(*(index_buffer + i));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads index data from a glTF primitive into a GHOST_XrPrimitive.
|
||||
*/
|
||||
static void load_index_accessor(const tinygltf::Model &gltf_model,
|
||||
const tinygltf::Accessor &accessor,
|
||||
GHOST_XrPrimitive &primitive)
|
||||
{
|
||||
if (accessor.type != TINYGLTF_TYPE_SCALAR) {
|
||||
throw GHOST_XrException("glTF: Accessor for indices specifies invalid 'type'.");
|
||||
}
|
||||
|
||||
if (accessor.bufferView == -1) {
|
||||
throw GHOST_XrException("glTF: Index accessor without bufferView is currently not supported.");
|
||||
}
|
||||
|
||||
const tinygltf::BufferView &buffer_view = gltf_model.bufferViews.at(accessor.bufferView);
|
||||
const tinygltf::Buffer &buffer = gltf_model.buffers.at(buffer_view.buffer);
|
||||
|
||||
if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) {
|
||||
read_indices<uint8_t>(accessor, buffer_view, buffer, primitive);
|
||||
}
|
||||
else if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) {
|
||||
read_indices<uint16_t>(accessor, buffer_view, buffer, primitive);
|
||||
}
|
||||
else if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT) {
|
||||
read_indices<uint32_t>(accessor, buffer_view, buffer, primitive);
|
||||
}
|
||||
else {
|
||||
throw GHOST_XrException("glTF: Accessor for indices specifies invalid 'componentType'.");
|
||||
}
|
||||
}
|
||||
|
||||
static GHOST_XrPrimitive read_primitive(const tinygltf::Model &gltf_model,
|
||||
const tinygltf::Primitive &gltf_primitive)
|
||||
{
|
||||
if (gltf_primitive.mode != TINYGLTF_MODE_TRIANGLES) {
|
||||
throw GHOST_XrException(
|
||||
"glTF: Unsupported primitive mode. Only TINYGLTF_MODE_TRIANGLES is supported.");
|
||||
}
|
||||
|
||||
GHOST_XrPrimitive primitive;
|
||||
|
||||
/* glTF vertex data is stored in an attribute dictionary.Loop through each attribute and insert
|
||||
* it into the GHOST_XrPrimitive. */
|
||||
for (const auto &[attr_name, accessor_idx] : gltf_primitive.attributes) {
|
||||
load_attribute_accessor(gltf_model, attr_name, accessor_idx, primitive);
|
||||
}
|
||||
|
||||
if (gltf_primitive.indices != -1) {
|
||||
/* If indices are specified for the glTF primitive, read them into the GHOST_XrPrimitive. */
|
||||
load_index_accessor(gltf_model, gltf_model.accessors.at(gltf_primitive.indices), primitive);
|
||||
}
|
||||
|
||||
return primitive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate node local and world transforms.
|
||||
*/
|
||||
static void calc_node_transforms(const tinygltf::Node &gltf_node,
|
||||
const float parent_transform[4][4],
|
||||
float r_local_transform[4][4],
|
||||
float r_world_transform[4][4])
|
||||
{
|
||||
/* A node may specify either a 4x4 matrix or TRS (Translation - Rotation - Scale) values, but not
|
||||
* both. */
|
||||
if (gltf_node.matrix.size() == 16) {
|
||||
const std::vector<double> &dm = gltf_node.matrix;
|
||||
float m[4][4] = {(float)dm[0],
|
||||
(float)dm[1],
|
||||
(float)dm[2],
|
||||
(float)dm[3],
|
||||
(float)dm[4],
|
||||
(float)dm[5],
|
||||
(float)dm[6],
|
||||
(float)dm[7],
|
||||
(float)dm[8],
|
||||
(float)dm[9],
|
||||
(float)dm[10],
|
||||
(float)dm[11],
|
||||
(float)dm[12],
|
||||
(float)dm[13],
|
||||
(float)dm[14],
|
||||
(float)dm[15]};
|
||||
memcpy(r_local_transform, m, sizeof(float) * 16);
|
||||
}
|
||||
else {
|
||||
/* No matrix is present, so construct a matrix from the TRS values (each one is optional). */
|
||||
std::vector<double> translation = gltf_node.translation;
|
||||
std::vector<double> rotation = gltf_node.rotation;
|
||||
std::vector<double> scale = gltf_node.scale;
|
||||
Eigen::Matrix4f &m = *(Eigen::Matrix4f *)r_local_transform;
|
||||
Eigen::Quaternionf q;
|
||||
Eigen::Matrix3f scalemat;
|
||||
|
||||
if (translation.size() != 3) {
|
||||
translation.resize(3);
|
||||
translation[0] = translation[1] = translation[2] = 0.0;
|
||||
}
|
||||
if (rotation.size() != 4) {
|
||||
rotation.resize(4);
|
||||
rotation[0] = rotation[1] = rotation[2] = 0.0;
|
||||
rotation[3] = 1.0;
|
||||
}
|
||||
if (scale.size() != 3) {
|
||||
scale.resize(3);
|
||||
scale[0] = scale[1] = scale[2] = 1.0;
|
||||
}
|
||||
|
||||
q.w() = (float)rotation[3];
|
||||
q.x() = (float)rotation[0];
|
||||
q.y() = (float)rotation[1];
|
||||
q.z() = (float)rotation[2];
|
||||
q.normalize();
|
||||
|
||||
scalemat.setIdentity();
|
||||
scalemat(0, 0) = (float)scale[0];
|
||||
scalemat(1, 1) = (float)scale[1];
|
||||
scalemat(2, 2) = (float)scale[2];
|
||||
|
||||
m.setIdentity();
|
||||
m.block<3, 3>(0, 0) = q.toRotationMatrix() * scalemat;
|
||||
m.block<3, 1>(0, 3) = Eigen::Vector3f(
|
||||
(float)translation[0], (float)translation[1], (float)translation[2]);
|
||||
}
|
||||
|
||||
*(Eigen::Matrix4f *)r_world_transform = *(Eigen::Matrix4f *)parent_transform *
|
||||
*(Eigen::Matrix4f *)r_local_transform;
|
||||
}
|
||||
|
||||
static void load_node(const tinygltf::Model &gltf_model,
|
||||
int gltf_node_id,
|
||||
int32_t parent_idx,
|
||||
const float parent_transform[4][4],
|
||||
const std::string &parent_name,
|
||||
const std::vector<XrControllerModelNodePropertiesMSFT> &node_properties,
|
||||
std::vector<GHOST_XrControllerModelVertex> &vertices,
|
||||
std::vector<uint32_t> &indices,
|
||||
std::vector<GHOST_XrControllerModelComponent> &components,
|
||||
std::vector<GHOST_XrControllerModelNode> &nodes,
|
||||
std::vector<int32_t> &node_state_indices)
|
||||
{
|
||||
const tinygltf::Node &gltf_node = gltf_model.nodes.at(gltf_node_id);
|
||||
float world_transform[4][4];
|
||||
|
||||
GHOST_XrControllerModelNode &node = nodes.emplace_back();
|
||||
const int32_t node_idx = (int32_t)(nodes.size() - 1);
|
||||
node.parent_idx = parent_idx;
|
||||
calc_node_transforms(gltf_node, parent_transform, node.local_transform, world_transform);
|
||||
|
||||
for (size_t i = 0; i < node_properties.size(); ++i) {
|
||||
if ((node_state_indices[i] < 0) && (parent_name == node_properties[i].parentNodeName) &&
|
||||
(gltf_node.name == node_properties[i].nodeName)) {
|
||||
node_state_indices[i] = node_idx;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (gltf_node.mesh != -1) {
|
||||
const tinygltf::Mesh &gltf_mesh = gltf_model.meshes.at(gltf_node.mesh);
|
||||
|
||||
GHOST_XrControllerModelComponent &component = components.emplace_back();
|
||||
node.component_idx = components.size() - 1;
|
||||
memcpy(component.transform, world_transform, sizeof(component.transform));
|
||||
component.vertex_offset = vertices.size();
|
||||
component.index_offset = indices.size();
|
||||
|
||||
for (const tinygltf::Primitive &gltf_primitive : gltf_mesh.primitives) {
|
||||
/* Read the primitive data from the glTF buffers. */
|
||||
const GHOST_XrPrimitive primitive = read_primitive(gltf_model, gltf_primitive);
|
||||
|
||||
const size_t start_vertex = vertices.size();
|
||||
size_t offset = start_vertex;
|
||||
size_t count = primitive.vertices.size();
|
||||
vertices.resize(offset + count);
|
||||
memcpy(vertices.data() + offset,
|
||||
primitive.vertices.data(),
|
||||
count * sizeof(decltype(primitive.vertices)::value_type));
|
||||
|
||||
offset = indices.size();
|
||||
count = primitive.indices.size();
|
||||
indices.resize(offset + count);
|
||||
for (size_t i = 0; i < count; i += 3) {
|
||||
indices[offset + i + 0] = start_vertex + primitive.indices[i + 0];
|
||||
indices[offset + i + 1] = start_vertex + primitive.indices[i + 2];
|
||||
indices[offset + i + 2] = start_vertex + primitive.indices[i + 1];
|
||||
}
|
||||
}
|
||||
|
||||
component.vertex_count = vertices.size() - component.vertex_offset;
|
||||
component.index_count = indices.size() - component.index_offset;
|
||||
}
|
||||
|
||||
/* Recursively load all children. */
|
||||
for (const int child_node_id : gltf_node.children) {
|
||||
load_node(gltf_model,
|
||||
child_node_id,
|
||||
node_idx,
|
||||
world_transform,
|
||||
gltf_node.name,
|
||||
node_properties,
|
||||
vertices,
|
||||
indices,
|
||||
components,
|
||||
nodes,
|
||||
node_state_indices);
|
||||
}
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name OpenXR Extension Functions
|
||||
*
|
||||
* \{ */
|
||||
|
||||
static PFN_xrGetControllerModelKeyMSFT g_xrGetControllerModelKeyMSFT = nullptr;
|
||||
static PFN_xrLoadControllerModelMSFT g_xrLoadControllerModelMSFT = nullptr;
|
||||
static PFN_xrGetControllerModelPropertiesMSFT g_xrGetControllerModelPropertiesMSFT = nullptr;
|
||||
static PFN_xrGetControllerModelStateMSFT g_xrGetControllerModelStateMSFT = nullptr;
|
||||
static XrInstance g_instance = XR_NULL_HANDLE;
|
||||
|
||||
#define INIT_EXTENSION_FUNCTION(name) \
|
||||
CHECK_XR( \
|
||||
xrGetInstanceProcAddr(instance, #name, reinterpret_cast<PFN_xrVoidFunction *>(&g_##name)), \
|
||||
"Failed to get pointer to extension function: " #name);
|
||||
|
||||
static void init_controller_model_extension_functions(XrInstance instance)
|
||||
{
|
||||
if (instance != g_instance) {
|
||||
g_instance = instance;
|
||||
g_xrGetControllerModelKeyMSFT = nullptr;
|
||||
g_xrLoadControllerModelMSFT = nullptr;
|
||||
g_xrGetControllerModelPropertiesMSFT = nullptr;
|
||||
g_xrGetControllerModelStateMSFT = nullptr;
|
||||
}
|
||||
|
||||
if (g_xrGetControllerModelKeyMSFT == nullptr) {
|
||||
INIT_EXTENSION_FUNCTION(xrGetControllerModelKeyMSFT);
|
||||
}
|
||||
if (g_xrLoadControllerModelMSFT == nullptr) {
|
||||
INIT_EXTENSION_FUNCTION(xrLoadControllerModelMSFT);
|
||||
}
|
||||
if (g_xrGetControllerModelPropertiesMSFT == nullptr) {
|
||||
INIT_EXTENSION_FUNCTION(xrGetControllerModelPropertiesMSFT);
|
||||
}
|
||||
if (g_xrGetControllerModelStateMSFT == nullptr) {
|
||||
INIT_EXTENSION_FUNCTION(xrGetControllerModelStateMSFT);
|
||||
}
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name GHOST_XrControllerModel
|
||||
*
|
||||
* \{ */
|
||||
|
||||
GHOST_XrControllerModel::GHOST_XrControllerModel(XrInstance instance,
|
||||
const char *subaction_path_str)
|
||||
{
|
||||
init_controller_model_extension_functions(instance);
|
||||
|
||||
CHECK_XR(xrStringToPath(instance, subaction_path_str, &m_subaction_path),
|
||||
(std::string("Failed to get user path \"") + subaction_path_str + "\".").data());
|
||||
}
|
||||
|
||||
GHOST_XrControllerModel::~GHOST_XrControllerModel()
|
||||
{
|
||||
if (m_load_task.valid()) {
|
||||
m_load_task.wait();
|
||||
}
|
||||
}
|
||||
|
||||
void GHOST_XrControllerModel::load(XrSession session)
|
||||
{
|
||||
if (m_data_loaded || m_load_task.valid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Get model key. */
|
||||
XrControllerModelKeyStateMSFT key_state{XR_TYPE_CONTROLLER_MODEL_KEY_STATE_MSFT};
|
||||
CHECK_XR(g_xrGetControllerModelKeyMSFT(session, m_subaction_path, &key_state),
|
||||
"Failed to get controller model key state.");
|
||||
|
||||
if (key_state.modelKey != XR_NULL_CONTROLLER_MODEL_KEY_MSFT) {
|
||||
m_model_key = key_state.modelKey;
|
||||
/* Load asynchronously. */
|
||||
m_load_task = std::async(std::launch::async,
|
||||
[&, session = session]() { return loadControllerModel(session); });
|
||||
}
|
||||
}
|
||||
|
||||
void GHOST_XrControllerModel::loadControllerModel(XrSession session)
|
||||
{
|
||||
/* Load binary buffers. */
|
||||
uint32_t buf_size = 0;
|
||||
CHECK_XR(g_xrLoadControllerModelMSFT(session, m_model_key, 0, &buf_size, nullptr),
|
||||
"Failed to get controller model buffer size.");
|
||||
|
||||
std::vector<uint8_t> buf((size_t)buf_size);
|
||||
CHECK_XR(g_xrLoadControllerModelMSFT(session, m_model_key, buf_size, &buf_size, buf.data()),
|
||||
"Failed to load controller model binary buffers.");
|
||||
|
||||
/* Convert to glTF model. */
|
||||
tinygltf::TinyGLTF gltf_loader;
|
||||
tinygltf::Model gltf_model;
|
||||
std::string err_msg;
|
||||
{
|
||||
/* Workaround for TINYGLTF_NO_STB_IMAGE define. Set custom image loader to prevent failure when
|
||||
* parsing image data. */
|
||||
auto load_img_func = [](tinygltf::Image *img,
|
||||
const int p0,
|
||||
std::string *p1,
|
||||
std::string *p2,
|
||||
int p3,
|
||||
int p4,
|
||||
const unsigned char *p5,
|
||||
int p6,
|
||||
void *user_pointer) -> bool {
|
||||
(void)img;
|
||||
(void)p0;
|
||||
(void)p1;
|
||||
(void)p2;
|
||||
(void)p3;
|
||||
(void)p4;
|
||||
(void)p5;
|
||||
(void)p6;
|
||||
(void)user_pointer;
|
||||
return true;
|
||||
};
|
||||
gltf_loader.SetImageLoader(load_img_func, nullptr);
|
||||
}
|
||||
|
||||
if (!gltf_loader.LoadBinaryFromMemory(&gltf_model, &err_msg, nullptr, buf.data(), buf_size)) {
|
||||
throw GHOST_XrException(("Failed to load glTF controller model: " + err_msg).c_str());
|
||||
}
|
||||
|
||||
/* Get node properties. */
|
||||
XrControllerModelPropertiesMSFT model_properties{XR_TYPE_CONTROLLER_MODEL_PROPERTIES_MSFT};
|
||||
model_properties.nodeCapacityInput = 0;
|
||||
CHECK_XR(g_xrGetControllerModelPropertiesMSFT(session, m_model_key, &model_properties),
|
||||
"Failed to get controller model node properties count.");
|
||||
|
||||
std::vector<XrControllerModelNodePropertiesMSFT> node_properties(
|
||||
model_properties.nodeCountOutput, {XR_TYPE_CONTROLLER_MODEL_NODE_PROPERTIES_MSFT});
|
||||
model_properties.nodeCapacityInput = (uint32_t)node_properties.size();
|
||||
model_properties.nodeProperties = node_properties.data();
|
||||
CHECK_XR(g_xrGetControllerModelPropertiesMSFT(session, m_model_key, &model_properties),
|
||||
"Failed to get controller model node properties.");
|
||||
|
||||
m_node_state_indices.resize(node_properties.size(), -1);
|
||||
|
||||
/* Get mesh vertex data. */
|
||||
const tinygltf::Scene &default_scene = gltf_model.scenes.at(
|
||||
(gltf_model.defaultScene == -1) ? 0 : gltf_model.defaultScene);
|
||||
const int32_t root_idx = -1;
|
||||
const std::string root_name = "";
|
||||
float root_transform[4][4] = {0};
|
||||
root_transform[0][0] = root_transform[1][1] = root_transform[2][2] = root_transform[3][3] = 1.0f;
|
||||
|
||||
for (const int node_id : default_scene.nodes) {
|
||||
load_node(gltf_model,
|
||||
node_id,
|
||||
root_idx,
|
||||
root_transform,
|
||||
root_name,
|
||||
node_properties,
|
||||
m_vertices,
|
||||
m_indices,
|
||||
m_components,
|
||||
m_nodes,
|
||||
m_node_state_indices);
|
||||
}
|
||||
|
||||
m_data_loaded = true;
|
||||
}
|
||||
|
||||
void GHOST_XrControllerModel::updateComponents(XrSession session)
|
||||
{
|
||||
if (!m_data_loaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Get node states. */
|
||||
XrControllerModelStateMSFT model_state{XR_TYPE_CONTROLLER_MODEL_STATE_MSFT};
|
||||
model_state.nodeCapacityInput = 0;
|
||||
CHECK_XR(g_xrGetControllerModelStateMSFT(session, m_model_key, &model_state),
|
||||
"Failed to get controller model node state count.");
|
||||
|
||||
const uint32_t count = model_state.nodeCountOutput;
|
||||
std::vector<XrControllerModelNodeStateMSFT> node_states(
|
||||
count, {XR_TYPE_CONTROLLER_MODEL_NODE_STATE_MSFT});
|
||||
model_state.nodeCapacityInput = count;
|
||||
model_state.nodeStates = node_states.data();
|
||||
CHECK_XR(g_xrGetControllerModelStateMSFT(session, m_model_key, &model_state),
|
||||
"Failed to get controller model node states.");
|
||||
|
||||
/* Update node local transforms. */
|
||||
assert(m_node_state_indices.size() == count);
|
||||
|
||||
for (uint32_t state_idx = 0; state_idx < count; ++state_idx) {
|
||||
const int32_t &node_idx = m_node_state_indices[state_idx];
|
||||
if (node_idx >= 0) {
|
||||
const XrPosef &pose = node_states[state_idx].nodePose;
|
||||
Eigen::Matrix4f &m = *(Eigen::Matrix4f *)m_nodes[node_idx].local_transform;
|
||||
Eigen::Quaternionf q(
|
||||
pose.orientation.w, pose.orientation.x, pose.orientation.y, pose.orientation.z);
|
||||
m.setIdentity();
|
||||
m.block<3, 3>(0, 0) = q.toRotationMatrix();
|
||||
m.block<3, 1>(0, 3) = Eigen::Vector3f(pose.position.x, pose.position.y, pose.position.z);
|
||||
}
|
||||
}
|
||||
|
||||
/* Calculate component transforms (in world space). */
|
||||
std::vector<Eigen::Matrix4f> world_transforms(m_nodes.size());
|
||||
uint32_t i = 0;
|
||||
for (const GHOST_XrControllerModelNode &node : m_nodes) {
|
||||
world_transforms[i] = (node.parent_idx >= 0) ? world_transforms[node.parent_idx] *
|
||||
*(Eigen::Matrix4f *)node.local_transform :
|
||||
*(Eigen::Matrix4f *)node.local_transform;
|
||||
if (node.component_idx >= 0) {
|
||||
memcpy(m_components[node.component_idx].transform,
|
||||
world_transforms[i].data(),
|
||||
sizeof(m_components[node.component_idx].transform));
|
||||
}
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
void GHOST_XrControllerModel::getData(GHOST_XrControllerModelData &r_data)
|
||||
{
|
||||
if (m_data_loaded) {
|
||||
r_data.count_vertices = (uint32_t)m_vertices.size();
|
||||
r_data.vertices = m_vertices.data();
|
||||
r_data.count_indices = (uint32_t)m_indices.size();
|
||||
r_data.indices = m_indices.data();
|
||||
r_data.count_components = (uint32_t)m_components.size();
|
||||
r_data.components = m_components.data();
|
||||
}
|
||||
else {
|
||||
r_data.count_vertices = 0;
|
||||
r_data.vertices = nullptr;
|
||||
r_data.count_indices = 0;
|
||||
r_data.indices = nullptr;
|
||||
r_data.count_components = 0;
|
||||
r_data.components = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/** \} */
|
59
intern/ghost/intern/GHOST_XrControllerModel.h
Normal file
59
intern/ghost/intern/GHOST_XrControllerModel.h
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/** \file
|
||||
* \ingroup GHOST
|
||||
*/
|
||||
|
||||
/* Note: Requires OpenXR headers to be included before this one for OpenXR types (XrInstance,
|
||||
* XrSession, etc.). */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <future>
|
||||
#include <vector>
|
||||
|
||||
struct GHOST_XrControllerModelNode;
|
||||
|
||||
/**
|
||||
* OpenXR glTF controller model.
|
||||
*/
|
||||
class GHOST_XrControllerModel {
|
||||
public:
|
||||
GHOST_XrControllerModel(XrInstance instance, const char *subaction_path);
|
||||
~GHOST_XrControllerModel();
|
||||
|
||||
void load(XrSession session);
|
||||
void updateComponents(XrSession session);
|
||||
void getData(GHOST_XrControllerModelData &r_data);
|
||||
|
||||
private:
|
||||
XrPath m_subaction_path = XR_NULL_PATH;
|
||||
XrControllerModelKeyMSFT m_model_key = XR_NULL_CONTROLLER_MODEL_KEY_MSFT;
|
||||
|
||||
std::future<void> m_load_task;
|
||||
std::atomic<bool> m_data_loaded = false;
|
||||
|
||||
std::vector<GHOST_XrControllerModelVertex> m_vertices;
|
||||
std::vector<uint32_t> m_indices;
|
||||
std::vector<GHOST_XrControllerModelComponent> m_components;
|
||||
std::vector<GHOST_XrControllerModelNode> m_nodes;
|
||||
/** Maps node states to nodes. */
|
||||
std::vector<int32_t> m_node_state_indices;
|
||||
|
||||
void loadControllerModel(XrSession session);
|
||||
};
|
@@ -30,6 +30,7 @@
|
||||
#include "GHOST_IXrGraphicsBinding.h"
|
||||
#include "GHOST_XrAction.h"
|
||||
#include "GHOST_XrContext.h"
|
||||
#include "GHOST_XrControllerModel.h"
|
||||
#include "GHOST_XrException.h"
|
||||
#include "GHOST_XrSwapchain.h"
|
||||
#include "GHOST_Xr_intern.h"
|
||||
@@ -52,6 +53,8 @@ struct OpenXRSessionData {
|
||||
std::vector<GHOST_XrSwapchain> swapchains;
|
||||
|
||||
std::map<std::string, GHOST_XrActionSet> action_sets;
|
||||
/* Controller models identified by subaction path. */
|
||||
std::map<std::string, GHOST_XrControllerModel> controller_models;
|
||||
};
|
||||
|
||||
struct GHOST_XrDrawInfo {
|
||||
@@ -916,3 +919,71 @@ void GHOST_XrSession::getActionCustomdataArray(const char *action_set_name,
|
||||
}
|
||||
|
||||
/** \} */ /* Actions */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Controller Model
|
||||
*
|
||||
* \{ */
|
||||
|
||||
bool GHOST_XrSession::loadControllerModel(const char *subaction_path)
|
||||
{
|
||||
if (!m_context->isExtensionEnabled(XR_MSFT_CONTROLLER_MODEL_EXTENSION_NAME)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
XrSession session = m_oxr->session;
|
||||
std::map<std::string, GHOST_XrControllerModel> &controller_models = m_oxr->controller_models;
|
||||
std::map<std::string, GHOST_XrControllerModel>::iterator it = controller_models.find(
|
||||
subaction_path);
|
||||
|
||||
if (it == controller_models.end()) {
|
||||
XrInstance instance = m_context->getInstance();
|
||||
it = controller_models
|
||||
.emplace(std::piecewise_construct,
|
||||
std::make_tuple(subaction_path),
|
||||
std::make_tuple(instance, subaction_path))
|
||||
.first;
|
||||
}
|
||||
|
||||
it->second.load(session);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GHOST_XrSession::unloadControllerModel(const char *subaction_path)
|
||||
{
|
||||
std::map<std::string, GHOST_XrControllerModel> &controller_models = m_oxr->controller_models;
|
||||
if (controller_models.find(subaction_path) != controller_models.end()) {
|
||||
controller_models.erase(subaction_path);
|
||||
}
|
||||
}
|
||||
|
||||
bool GHOST_XrSession::updateControllerModelComponents(const char *subaction_path)
|
||||
{
|
||||
XrSession session = m_oxr->session;
|
||||
std::map<std::string, GHOST_XrControllerModel>::iterator it = m_oxr->controller_models.find(
|
||||
subaction_path);
|
||||
if (it == m_oxr->controller_models.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
it->second.updateComponents(session);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GHOST_XrSession::getControllerModelData(const char *subaction_path,
|
||||
GHOST_XrControllerModelData &r_data)
|
||||
{
|
||||
std::map<std::string, GHOST_XrControllerModel>::iterator it = m_oxr->controller_models.find(
|
||||
subaction_path);
|
||||
if (it == m_oxr->controller_models.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
it->second.getData(r_data);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** \} */ /* Controller Model */
|
@@ -69,10 +69,8 @@ class GHOST_XrSession {
|
||||
const char *const *profile_paths);
|
||||
bool attachActionSets();
|
||||
|
||||
/**
|
||||
* Action functions to be called post-session start.
|
||||
* \param action_set_name: When `nullptr`, all attached action sets will be synced.
|
||||
*/
|
||||
/** Action functions to be called post-session start. */
|
||||
/** param action_set_name : When `nullptr`, all attached action sets will be synced. */
|
||||
bool syncActions(const char *action_set_name = nullptr);
|
||||
bool applyHapticAction(const char *action_set_name,
|
||||
const char *action_name,
|
||||
@@ -84,12 +82,18 @@ class GHOST_XrSession {
|
||||
const char *action_name,
|
||||
const char *subaction_path);
|
||||
|
||||
/* Custom data (owned by Blender, not GHOST) accessors. */
|
||||
/** Custom data (owned by Blender, not GHOST) accessors. */
|
||||
void *getActionSetCustomdata(const char *action_set_name);
|
||||
void *getActionCustomdata(const char *action_set_name, const char *action_name);
|
||||
uint32_t getActionCount(const char *action_set_name);
|
||||
void getActionCustomdataArray(const char *action_set_name, void **r_customdata_array);
|
||||
|
||||
/** Controller model functions. */
|
||||
bool loadControllerModel(const char *subaction_path);
|
||||
void unloadControllerModel(const char *subaction_path);
|
||||
bool updateControllerModelComponents(const char *subaction_path);
|
||||
bool getControllerModelData(const char *subaction_path, GHOST_XrControllerModelData &r_data);
|
||||
|
||||
private:
|
||||
/** Pointer back to context managing this session. Would be nice to avoid, but needed to access
|
||||
* custom callbacks set before session start. */
|
||||
|
@@ -21,6 +21,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string.h>
|
||||
#include <vector>
|
||||
|
||||
#include "GHOST_Xr_openxr_includes.h"
|
||||
|
@@ -89,7 +89,7 @@ typedef struct localListBase {
|
||||
void *first, *last;
|
||||
} localListBase;
|
||||
|
||||
/* note: keep this struct aligned (e.g., irix/gcc) - Hos */
|
||||
/* NOTE(@hos): keep this struct aligned (e.g., IRIX/GCC). */
|
||||
typedef struct MemHead {
|
||||
int tag1;
|
||||
size_t len;
|
||||
@@ -98,9 +98,8 @@ typedef struct MemHead {
|
||||
const char *nextname;
|
||||
int tag2;
|
||||
short pad1;
|
||||
short alignment; /* if non-zero aligned alloc was used
|
||||
* and alignment is stored here.
|
||||
*/
|
||||
/* if non-zero aligned allocation was used and alignment is stored here. */
|
||||
short alignment;
|
||||
#ifdef DEBUG_MEMCOUNTER
|
||||
int _count;
|
||||
#endif
|
||||
|
Submodule release/scripts/addons updated: f86f25e622...c64726810b
@@ -219,7 +219,7 @@ def enable_addons(addons=None, support=None, disable=False, check_only=False):
|
||||
try:
|
||||
import bpy
|
||||
except ModuleNotFoundError:
|
||||
print("Could not import bpy, enable_addons must be run from whithin Blender.")
|
||||
print("Could not import bpy, enable_addons must be run from within Blender.")
|
||||
return
|
||||
|
||||
if addons is None:
|
||||
|
@@ -54,11 +54,26 @@ def update_factory_startup_grease_pencils():
|
||||
gpd.onion_keyframe_type = 'ALL'
|
||||
|
||||
|
||||
def update_factory_startup_theme():
|
||||
# To prevent saving over the current theme Preferences,
|
||||
# store the current state of use_preferences_save to use later.
|
||||
preferences = bpy.context.preferences
|
||||
save_preferences_state = preferences.use_preferences_save
|
||||
|
||||
# Turn use_preferences_save off and set header background alpha.
|
||||
preferences.use_preferences_save = False
|
||||
preferences.themes['Default'].view_3d.space.header[3] = 0.8
|
||||
|
||||
# Restore the original use_preferences_save status.
|
||||
preferences.use_preferences_save = save_preferences_state
|
||||
|
||||
|
||||
@persistent
|
||||
def load_handler(_):
|
||||
update_factory_startup_screens()
|
||||
update_factory_startup_scenes()
|
||||
update_factory_startup_grease_pencils()
|
||||
update_factory_startup_theme()
|
||||
|
||||
|
||||
def register():
|
||||
|
@@ -46,44 +46,85 @@ def selected_sequences_len(context):
|
||||
|
||||
def draw_color_balance(layout, color_balance):
|
||||
|
||||
layout.prop(color_balance, "correction_method")
|
||||
|
||||
layout.use_property_split = False
|
||||
|
||||
flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
|
||||
col = flow.column()
|
||||
|
||||
box = col.box()
|
||||
split = box.split(factor=0.35)
|
||||
col = split.column(align=True)
|
||||
col.label(text="Lift:")
|
||||
col.separator()
|
||||
col.separator()
|
||||
col.prop(color_balance, "lift", text="")
|
||||
col.prop(color_balance, "invert_lift", text="Invert", icon='ARROW_LEFTRIGHT')
|
||||
split.template_color_picker(color_balance, "lift", value_slider=True, cubic=True)
|
||||
if color_balance.correction_method == 'LIFT_GAMMA_GAIN':
|
||||
col = flow.column()
|
||||
|
||||
col = flow.column()
|
||||
box = col.box()
|
||||
split = box.split(factor=0.35)
|
||||
col = split.column(align=True)
|
||||
col.label(text="Lift:")
|
||||
col.separator()
|
||||
col.separator()
|
||||
col.prop(color_balance, "lift", text="")
|
||||
col.prop(color_balance, "invert_lift", text="Invert", icon='ARROW_LEFTRIGHT')
|
||||
split.template_color_picker(color_balance, "lift", value_slider=True, cubic=True)
|
||||
|
||||
box = col.box()
|
||||
split = box.split(factor=0.35)
|
||||
col = split.column(align=True)
|
||||
col.label(text="Gamma:")
|
||||
col.separator()
|
||||
col.separator()
|
||||
col.prop(color_balance, "gamma", text="")
|
||||
col.prop(color_balance, "invert_gamma", text="Invert", icon='ARROW_LEFTRIGHT')
|
||||
split.template_color_picker(color_balance, "gamma", value_slider=True, lock_luminosity=True, cubic=True)
|
||||
col = flow.column()
|
||||
|
||||
col = flow.column()
|
||||
box = col.box()
|
||||
split = box.split(factor=0.35)
|
||||
col = split.column(align=True)
|
||||
col.label(text="Gamma:")
|
||||
col.separator()
|
||||
col.separator()
|
||||
col.prop(color_balance, "gamma", text="")
|
||||
col.prop(color_balance, "invert_gamma", text="Invert", icon='ARROW_LEFTRIGHT')
|
||||
split.template_color_picker(color_balance, "gamma", value_slider=True, lock_luminosity=True, cubic=True)
|
||||
|
||||
box = col.box()
|
||||
split = box.split(factor=0.35)
|
||||
col = split.column(align=True)
|
||||
col.label(text="Gain:")
|
||||
col.separator()
|
||||
col.separator()
|
||||
col.prop(color_balance, "gain", text="")
|
||||
col.prop(color_balance, "invert_gain", text="Invert", icon='ARROW_LEFTRIGHT')
|
||||
split.template_color_picker(color_balance, "gain", value_slider=True, lock_luminosity=True, cubic=True)
|
||||
col = flow.column()
|
||||
|
||||
box = col.box()
|
||||
split = box.split(factor=0.35)
|
||||
col = split.column(align=True)
|
||||
col.label(text="Gain:")
|
||||
col.separator()
|
||||
col.separator()
|
||||
col.prop(color_balance, "gain", text="")
|
||||
col.prop(color_balance, "invert_gain", text="Invert", icon='ARROW_LEFTRIGHT')
|
||||
split.template_color_picker(color_balance, "gain", value_slider=True, lock_luminosity=True, cubic=True)
|
||||
|
||||
elif color_balance.correction_method == 'OFFSET_POWER_SLOPE':
|
||||
col = flow.column()
|
||||
|
||||
box = col.box()
|
||||
split = box.split(factor=0.35)
|
||||
col = split.column(align=True)
|
||||
col.label(text="Offset:")
|
||||
col.separator()
|
||||
col.separator()
|
||||
col.prop(color_balance, "offset", text="")
|
||||
col.prop(color_balance, "invert_offset", text="Invert", icon='ARROW_LEFTRIGHT')
|
||||
split.template_color_picker(color_balance, "offset", value_slider=True, cubic=True)
|
||||
|
||||
col = flow.column()
|
||||
|
||||
box = col.box()
|
||||
split = box.split(factor=0.35)
|
||||
col = split.column(align=True)
|
||||
col.label(text="Power:")
|
||||
col.separator()
|
||||
col.separator()
|
||||
col.prop(color_balance, "power", text="")
|
||||
col.prop(color_balance, "invert_power", text="Invert", icon='ARROW_LEFTRIGHT')
|
||||
split.template_color_picker(color_balance, "power", value_slider=True, cubic=True)
|
||||
|
||||
col = flow.column()
|
||||
|
||||
box = col.box()
|
||||
split = box.split(factor=0.35)
|
||||
col = split.column(align=True)
|
||||
col.label(text="Slope:")
|
||||
col.separator()
|
||||
col.separator()
|
||||
col.prop(color_balance, "slope", text="")
|
||||
col.prop(color_balance, "invert_slope", text="Invert", icon='ARROW_LEFTRIGHT')
|
||||
split.template_color_picker(color_balance, "slope", value_slider=True, cubic=True)
|
||||
|
||||
|
||||
class SEQUENCER_PT_active_tool(ToolActivePanelHelper, Panel):
|
||||
@@ -148,8 +189,12 @@ class SEQUENCER_HT_header(Header):
|
||||
if st.view_type in {'SEQUENCER', 'SEQUENCER_PREVIEW'}:
|
||||
row = layout.row(align=True)
|
||||
row.prop(sequencer_tool_settings, "overlap_mode", text="")
|
||||
|
||||
if st.view_type == 'SEQUENCER_PREVIEW':
|
||||
row = layout.row(align=True)
|
||||
row.prop(sequencer_tool_settings, "pivot_point", text="", icon_only=True)
|
||||
|
||||
if st.view_type in {'SEQUENCER', 'SEQUENCER_PREVIEW'}:
|
||||
row = layout.row(align=True)
|
||||
row.prop(tool_settings, "use_snap_sequencer", text="")
|
||||
sub = row.row(align=True)
|
||||
|
@@ -279,6 +279,7 @@ shader_node_categories = [
|
||||
]),
|
||||
ShaderNodeCategory("SH_NEW_CONVERTOR", "Converter", items=[
|
||||
NodeItem("ShaderNodeMapRange"),
|
||||
NodeItem("ShaderNodeFloatCurve"),
|
||||
NodeItem("ShaderNodeClamp"),
|
||||
NodeItem("ShaderNodeMath"),
|
||||
NodeItem("ShaderNodeValToRGB"),
|
||||
@@ -524,7 +525,11 @@ geometry_node_categories = [
|
||||
NodeItem("GeometryNodeCurveFill"),
|
||||
NodeItem("GeometryNodeCurveTrim"),
|
||||
NodeItem("GeometryNodeCurveLength"),
|
||||
NodeItem("GeometryNodeCurveSplineType"),
|
||||
NodeItem("GeometryNodeSplineLength"),
|
||||
NodeItem("GeometryNodeCurveSubdivide"),
|
||||
NodeItem("GeometryNodeCurveParameter"),
|
||||
NodeItem("GeometryNodeCurveSetHandles"),
|
||||
NodeItem("GeometryNodeInputTangent"),
|
||||
NodeItem("GeometryNodeCurveSample"),
|
||||
NodeItem("GeometryNodeCurveFillet"),
|
||||
@@ -615,9 +620,11 @@ geometry_node_categories = [
|
||||
]),
|
||||
GeometryNodeCategory("GEO_UTILITIES", "Utilities", items=[
|
||||
NodeItem("ShaderNodeMapRange"),
|
||||
NodeItem("ShaderNodeFloatCurve"),
|
||||
NodeItem("ShaderNodeClamp"),
|
||||
NodeItem("ShaderNodeMath"),
|
||||
NodeItem("FunctionNodeBooleanMath"),
|
||||
NodeItem("FunctionNodeRotateEuler"),
|
||||
NodeItem("FunctionNodeFloatCompare"),
|
||||
NodeItem("FunctionNodeFloatToInt"),
|
||||
NodeItem("GeometryNodeSwitch"),
|
||||
|
@@ -14,11 +14,17 @@
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
/** \file
|
||||
* \ingroup blendthumb
|
||||
*
|
||||
* Thumbnail from Blend file extraction for MS-Windows (DLL).
|
||||
*/
|
||||
|
||||
#include <new>
|
||||
#include <objbase.h>
|
||||
#include <shlobj.h> // For SHChangeNotify
|
||||
#include <shlobj.h> /* For #SHChangeNotify */
|
||||
#include <shlwapi.h>
|
||||
#include <thumbcache.h> // For IThumbnailProvider.
|
||||
#include <thumbcache.h> /* For IThumbnailProvider */
|
||||
|
||||
extern HRESULT CBlendThumb_CreateInstance(REFIID riid, void **ppv);
|
||||
|
||||
@@ -33,16 +39,16 @@ struct CLASS_OBJECT_INIT {
|
||||
PFNCREATEINSTANCE pfnCreate;
|
||||
};
|
||||
|
||||
// add classes supported by this module here
|
||||
/* Add classes supported by this module here. */
|
||||
const CLASS_OBJECT_INIT c_rgClassObjectInit[] = {
|
||||
{&CLSID_BlendThumbHandler, CBlendThumb_CreateInstance}};
|
||||
|
||||
long g_cRefModule = 0;
|
||||
|
||||
// Handle the DLL's module
|
||||
HINSTANCE g_hInst = NULL;
|
||||
/** Handle the DLL's module */
|
||||
HINSTANCE g_hInst = nullptr;
|
||||
|
||||
// Standard DLL functions
|
||||
/** Standard DLL functions. */
|
||||
STDAPI_(BOOL) DllMain(HINSTANCE hInstance, DWORD dwReason, void *)
|
||||
{
|
||||
if (dwReason == DLL_PROCESS_ATTACH) {
|
||||
@@ -54,7 +60,7 @@ STDAPI_(BOOL) DllMain(HINSTANCE hInstance, DWORD dwReason, void *)
|
||||
|
||||
STDAPI DllCanUnloadNow()
|
||||
{
|
||||
// Only allow the DLL to be unloaded after all outstanding references have been released
|
||||
/* Only allow the DLL to be unloaded after all outstanding references have been released. */
|
||||
return (g_cRefModule == 0) ? S_OK : S_FALSE;
|
||||
}
|
||||
|
||||
@@ -76,7 +82,7 @@ class CClassFactory : public IClassFactory {
|
||||
REFIID riid,
|
||||
void **ppv)
|
||||
{
|
||||
*ppv = NULL;
|
||||
*ppv = nullptr;
|
||||
HRESULT hr = CLASS_E_CLASSNOTAVAILABLE;
|
||||
for (size_t i = 0; i < cClassObjectInits; i++) {
|
||||
if (clsid == *pClassObjectInits[i].pClsid) {
|
||||
@@ -87,7 +93,8 @@ class CClassFactory : public IClassFactory {
|
||||
hr = pClassFactory->QueryInterface(riid, ppv);
|
||||
pClassFactory->Release();
|
||||
}
|
||||
break; // match found
|
||||
/* Match found. */
|
||||
break;
|
||||
}
|
||||
}
|
||||
return hr;
|
||||
@@ -98,7 +105,7 @@ class CClassFactory : public IClassFactory {
|
||||
DllAddRef();
|
||||
}
|
||||
|
||||
// IUnknown
|
||||
/** #IUnknown */
|
||||
IFACEMETHODIMP QueryInterface(REFIID riid, void **ppv)
|
||||
{
|
||||
static const QITAB qit[] = {QITABENT(CClassFactory, IClassFactory), {0}};
|
||||
@@ -119,7 +126,7 @@ class CClassFactory : public IClassFactory {
|
||||
return cRef;
|
||||
}
|
||||
|
||||
// IClassFactory
|
||||
/** #IClassFactory */
|
||||
IFACEMETHODIMP CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppv)
|
||||
{
|
||||
return punkOuter ? CLASS_E_NOAGGREGATION : _pfnCreate(riid, ppv);
|
||||
@@ -152,33 +159,37 @@ STDAPI DllGetClassObject(REFCLSID clsid, REFIID riid, void **ppv)
|
||||
clsid, c_rgClassObjectInit, ARRAYSIZE(c_rgClassObjectInit), riid, ppv);
|
||||
}
|
||||
|
||||
// A struct to hold the information required for a registry entry
|
||||
|
||||
/**
|
||||
* A struct to hold the information required for a registry entry.
|
||||
*/
|
||||
struct REGISTRY_ENTRY {
|
||||
HKEY hkeyRoot;
|
||||
PCWSTR pszKeyName;
|
||||
PCWSTR pszValueName;
|
||||
DWORD dwValueType;
|
||||
PCWSTR pszData; // These two fields could/should have been a union, but C++
|
||||
DWORD dwData; // only lets you initialize the first field in a union.
|
||||
/** These two fields could/should have been a union, but C++ */
|
||||
PCWSTR pszData;
|
||||
/** Only lets you initialize the first field in a union. */
|
||||
DWORD dwData;
|
||||
};
|
||||
|
||||
// Creates a registry key (if needed) and sets the default value of the key
|
||||
|
||||
/**
|
||||
* Creates a registry key (if needed) and sets the default value of the key.
|
||||
*/
|
||||
HRESULT CreateRegKeyAndSetValue(const REGISTRY_ENTRY *pRegistryEntry)
|
||||
{
|
||||
HKEY hKey;
|
||||
HRESULT hr = HRESULT_FROM_WIN32(RegCreateKeyExW(pRegistryEntry->hkeyRoot,
|
||||
pRegistryEntry->pszKeyName,
|
||||
0,
|
||||
NULL,
|
||||
nullptr,
|
||||
REG_OPTION_NON_VOLATILE,
|
||||
KEY_SET_VALUE,
|
||||
NULL,
|
||||
nullptr,
|
||||
&hKey,
|
||||
NULL));
|
||||
nullptr));
|
||||
if (SUCCEEDED(hr)) {
|
||||
// All this just to support REG_DWORD...
|
||||
/* All this just to support #REG_DWORD. */
|
||||
DWORD size;
|
||||
DWORD data;
|
||||
BYTE *lpData = (LPBYTE)pRegistryEntry->pszData;
|
||||
@@ -202,9 +213,9 @@ HRESULT CreateRegKeyAndSetValue(const REGISTRY_ENTRY *pRegistryEntry)
|
||||
return hr;
|
||||
}
|
||||
|
||||
//
|
||||
// Registers this COM server
|
||||
//
|
||||
/**
|
||||
* Registers this COM server.
|
||||
*/
|
||||
STDAPI DllRegisterServer()
|
||||
{
|
||||
HRESULT hr;
|
||||
@@ -216,15 +227,15 @@ STDAPI DllRegisterServer()
|
||||
}
|
||||
else {
|
||||
const REGISTRY_ENTRY rgRegistryEntries[] = {
|
||||
// RootKey KeyName ValueName ValueType Data
|
||||
/* `RootKey KeyName ValueName ValueType Data` */
|
||||
{HKEY_CURRENT_USER,
|
||||
L"Software\\Classes\\CLSID\\" SZ_CLSID_BLENDTHUMBHANDLER,
|
||||
NULL,
|
||||
nullptr,
|
||||
REG_SZ,
|
||||
SZ_BLENDTHUMBHANDLER},
|
||||
{HKEY_CURRENT_USER,
|
||||
L"Software\\Classes\\CLSID\\" SZ_CLSID_BLENDTHUMBHANDLER L"\\InProcServer32",
|
||||
NULL,
|
||||
nullptr,
|
||||
REG_SZ,
|
||||
szModuleName},
|
||||
{HKEY_CURRENT_USER,
|
||||
@@ -237,10 +248,10 @@ STDAPI DllRegisterServer()
|
||||
L"Treatment",
|
||||
REG_DWORD,
|
||||
0,
|
||||
0}, // doesn't appear to do anything...
|
||||
0}, /* This doesn't appear to do anything. */
|
||||
{HKEY_CURRENT_USER,
|
||||
L"Software\\Classes\\.blend\\ShellEx\\{e357fccd-a995-4576-b01f-234630154e96}",
|
||||
NULL,
|
||||
nullptr,
|
||||
REG_SZ,
|
||||
SZ_CLSID_BLENDTHUMBHANDLER},
|
||||
};
|
||||
@@ -251,17 +262,17 @@ STDAPI DllRegisterServer()
|
||||
}
|
||||
}
|
||||
if (SUCCEEDED(hr)) {
|
||||
// This tells the shell to invalidate the thumbnail cache. This is important because any
|
||||
// .blend files viewed before registering this handler would otherwise show cached blank
|
||||
// thumbnails.
|
||||
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
|
||||
/* This tells the shell to invalidate the thumbnail cache.
|
||||
* This is important because any `.blend` files viewed before registering this handler
|
||||
* would otherwise show cached blank thumbnails. */
|
||||
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);
|
||||
}
|
||||
return hr;
|
||||
}
|
||||
|
||||
//
|
||||
// Unregisters this COM server
|
||||
//
|
||||
/**
|
||||
* Unregisters this COM server
|
||||
*/
|
||||
STDAPI DllUnregisterServer()
|
||||
{
|
||||
HRESULT hr = S_OK;
|
||||
@@ -270,11 +281,11 @@ STDAPI DllUnregisterServer()
|
||||
L"Software\\Classes\\CLSID\\" SZ_CLSID_BLENDTHUMBHANDLER,
|
||||
L"Software\\Classes\\.blend\\ShellEx\\{e357fccd-a995-4576-b01f-234630154e96}"};
|
||||
|
||||
// Delete the registry entries
|
||||
/* Delete the registry entries. */
|
||||
for (int i = 0; i < ARRAYSIZE(rgpszKeys) && SUCCEEDED(hr); i++) {
|
||||
hr = HRESULT_FROM_WIN32(RegDeleteTreeW(HKEY_CURRENT_USER, rgpszKeys[i]));
|
||||
if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) {
|
||||
// If the registry entry has already been deleted, say S_OK.
|
||||
/* If the registry entry has already been deleted, say S_OK. */
|
||||
hr = S_OK;
|
||||
}
|
||||
}
|
||||
|
@@ -26,10 +26,13 @@
|
||||
|
||||
#include "BLI_function_ref.hh"
|
||||
#include "BLI_map.hh"
|
||||
#include "BLI_set.hh"
|
||||
#include "BLI_string_ref.hh"
|
||||
#include "BLI_uuid.h"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include "BKE_asset_catalog_path.hh"
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
@@ -38,7 +41,6 @@
|
||||
namespace blender::bke {
|
||||
|
||||
using CatalogID = bUUID;
|
||||
using CatalogPath = std::string;
|
||||
using CatalogPathComponent = std::string;
|
||||
/* Would be nice to be able to use `std::filesystem::path` for this, but it's currently not
|
||||
* available on the minimum macOS target version. */
|
||||
@@ -47,12 +49,12 @@ using CatalogFilePath = std::string;
|
||||
class AssetCatalog;
|
||||
class AssetCatalogDefinitionFile;
|
||||
class AssetCatalogTree;
|
||||
class AssetCatalogFilter;
|
||||
|
||||
/* Manages the asset catalogs of a single asset library (i.e. of catalogs defined in a single
|
||||
* directory hierarchy). */
|
||||
class AssetCatalogService {
|
||||
public:
|
||||
static const char PATH_SEPARATOR;
|
||||
static const CatalogFilePath DEFAULT_CATALOG_FILENAME;
|
||||
|
||||
public:
|
||||
@@ -94,15 +96,23 @@ class AssetCatalogService {
|
||||
void merge_from_disk_before_writing();
|
||||
|
||||
/** Return catalog with the given ID. Return nullptr if not found. */
|
||||
AssetCatalog *find_catalog(CatalogID catalog_id);
|
||||
AssetCatalog *find_catalog(CatalogID catalog_id) const;
|
||||
|
||||
/** Return first catalog with the given path. Return nullptr if not found. This is not an
|
||||
* efficient call as it's just a linear search over the catalogs. */
|
||||
AssetCatalog *find_catalog_by_path(const CatalogPath &path) const;
|
||||
AssetCatalog *find_catalog_by_path(const AssetCatalogPath &path) const;
|
||||
|
||||
/**
|
||||
* Create a filter object that can be used to determine whether an asset belongs to the given
|
||||
* catalog, or any of the catalogs in the sub-tree rooted at the given catalog.
|
||||
*
|
||||
* \see #AssetCatalogFilter
|
||||
*/
|
||||
AssetCatalogFilter create_catalog_filter(CatalogID active_catalog_id) const;
|
||||
|
||||
/** Create a catalog with some sensible auto-generated catalog ID.
|
||||
* The catalog will be saved to the default catalog file.*/
|
||||
AssetCatalog *create_catalog(const CatalogPath &catalog_path);
|
||||
AssetCatalog *create_catalog(const AssetCatalogPath &catalog_path);
|
||||
|
||||
/**
|
||||
* Soft-delete the catalog, ensuring it actually gets deleted when the catalog definition file is
|
||||
@@ -112,7 +122,7 @@ class AssetCatalogService {
|
||||
/**
|
||||
* Update the catalog path, also updating the catalog path of all sub-catalogs.
|
||||
*/
|
||||
void update_catalog_path(CatalogID catalog_id, const CatalogPath &new_catalog_path);
|
||||
void update_catalog_path(CatalogID catalog_id, const AssetCatalogPath &new_catalog_path);
|
||||
|
||||
AssetCatalogTree *get_catalog_tree();
|
||||
|
||||
@@ -124,7 +134,7 @@ class AssetCatalogService {
|
||||
Map<CatalogID, std::unique_ptr<AssetCatalog>> catalogs_;
|
||||
Map<CatalogID, std::unique_ptr<AssetCatalog>> deleted_catalogs_;
|
||||
std::unique_ptr<AssetCatalogDefinitionFile> catalog_definition_file_;
|
||||
std::unique_ptr<AssetCatalogTree> catalog_tree_;
|
||||
std::unique_ptr<AssetCatalogTree> catalog_tree_ = std::make_unique<AssetCatalogTree>();
|
||||
CatalogFilePath asset_library_root_;
|
||||
|
||||
void load_directory_recursive(const CatalogFilePath &directory_path);
|
||||
@@ -150,6 +160,11 @@ class AssetCatalogService {
|
||||
|
||||
std::unique_ptr<AssetCatalogTree> read_into_tree();
|
||||
void rebuild_tree();
|
||||
|
||||
/**
|
||||
* For every catalog, ensure that its parent path also has a known catalog.
|
||||
*/
|
||||
void create_missing_catalogs();
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -172,7 +187,7 @@ class AssetCatalogTreeItem {
|
||||
StringRef get_name() const;
|
||||
/** Return the full catalog path, defined as the name of this catalog prefixed by the full
|
||||
* catalog path of its parent and a separator. */
|
||||
CatalogPath catalog_path() const;
|
||||
AssetCatalogPath catalog_path() const;
|
||||
int count_parents() const;
|
||||
bool has_children() const;
|
||||
|
||||
@@ -280,10 +295,10 @@ class AssetCatalogDefinitionFile {
|
||||
class AssetCatalog {
|
||||
public:
|
||||
AssetCatalog() = default;
|
||||
AssetCatalog(CatalogID catalog_id, const CatalogPath &path, const std::string &simple_name);
|
||||
AssetCatalog(CatalogID catalog_id, const AssetCatalogPath &path, const std::string &simple_name);
|
||||
|
||||
CatalogID catalog_id;
|
||||
CatalogPath path;
|
||||
AssetCatalogPath path;
|
||||
/**
|
||||
* Simple, human-readable name for the asset catalog. This is stored on assets alongside the
|
||||
* catalog ID; the catalog ID is a UUID that is not human-readable,
|
||||
@@ -297,27 +312,17 @@ class AssetCatalog {
|
||||
bool is_deleted = false;
|
||||
} flags;
|
||||
|
||||
/**
|
||||
* \return true only if this catalog's path is contained within the given path.
|
||||
* When this catalog's path is equal to the given path, return true as well.
|
||||
*
|
||||
* Note that non-normalized paths (so for example starting or ending with a slash) are not
|
||||
* supported, and result in undefined behavior.
|
||||
*/
|
||||
bool is_contained_in(const CatalogPath &other_path) const;
|
||||
|
||||
/**
|
||||
* Create a new Catalog with the given path, auto-generating a sensible catalog simple-name.
|
||||
*
|
||||
* NOTE: the given path will be cleaned up (trailing spaces removed, etc.), so the returned
|
||||
* `AssetCatalog`'s path differ from the given one.
|
||||
*/
|
||||
static std::unique_ptr<AssetCatalog> from_path(const CatalogPath &path);
|
||||
static CatalogPath cleanup_path(const CatalogPath &path);
|
||||
static std::unique_ptr<AssetCatalog> from_path(const AssetCatalogPath &path);
|
||||
|
||||
protected:
|
||||
/** Generate a sensible catalog ID for the given path. */
|
||||
static std::string sensible_simple_name_for_path(const CatalogPath &path);
|
||||
static std::string sensible_simple_name_for_path(const AssetCatalogPath &path);
|
||||
};
|
||||
|
||||
/** Comparator for asset catalogs, ordering by (path, UUID). */
|
||||
@@ -336,4 +341,20 @@ struct AssetCatalogPathCmp {
|
||||
* Being a set, duplicates are removed. The catalog's simple name is ignored in this. */
|
||||
using AssetCatalogOrderedSet = std::set<const AssetCatalog *, AssetCatalogPathCmp>;
|
||||
|
||||
/**
|
||||
* Filter that can determine whether an asset should be visible or not, based on its catalog ID.
|
||||
*
|
||||
* \see AssetCatalogService::create_filter()
|
||||
*/
|
||||
class AssetCatalogFilter {
|
||||
public:
|
||||
bool contains(CatalogID asset_catalog_id) const;
|
||||
|
||||
protected:
|
||||
friend AssetCatalogService;
|
||||
const Set<CatalogID> matching_catalog_ids;
|
||||
|
||||
explicit AssetCatalogFilter(Set<CatalogID> &&matching_catalog_ids);
|
||||
};
|
||||
|
||||
} // namespace blender::bke
|
||||
|
143
source/blender/blenkernel/BKE_asset_catalog_path.hh
Normal file
143
source/blender/blenkernel/BKE_asset_catalog_path.hh
Normal file
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/** \file
|
||||
* \ingroup bke
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef __cplusplus
|
||||
# error This is a C++ header.
|
||||
#endif
|
||||
|
||||
#include "BLI_function_ref.hh"
|
||||
#include "BLI_string_ref.hh"
|
||||
#include "BLI_sys_types.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace blender::bke {
|
||||
|
||||
/**
|
||||
* Location of an Asset Catalog in the catalog tree, denoted by slash-separated path components.
|
||||
*
|
||||
* Each path component is a string that is not allowed to have slashes or colons. The latter is to
|
||||
* make things easy to save in the colon-delimited Catalog Definition File format.
|
||||
*
|
||||
* The path of a catalog determines where in the catalog hierarchy the catalog is shown. Examples
|
||||
* are "Characters/Ellie/Poses/Hand" or "Kit_bash/City/Skyscrapers". The path looks like a
|
||||
* file-system path, with a few differences:
|
||||
*
|
||||
* - Only slashes are used as path component separators.
|
||||
* - All paths are absolute, so there is no need for a leading slash.
|
||||
*
|
||||
* See https://wiki.blender.org/wiki/Source/Architecture/Asset_System/Catalogs
|
||||
*
|
||||
* Paths are stored as byte sequences, and assumed to be UTF-8.
|
||||
*/
|
||||
class AssetCatalogPath {
|
||||
friend std::ostream &operator<<(std::ostream &stream, const AssetCatalogPath &path_to_append);
|
||||
|
||||
private:
|
||||
/**
|
||||
* The path itself, such as "Agents/Secret/327".
|
||||
*/
|
||||
std::string path_;
|
||||
|
||||
public:
|
||||
static const char SEPARATOR;
|
||||
|
||||
AssetCatalogPath() = delete;
|
||||
AssetCatalogPath(StringRef path);
|
||||
AssetCatalogPath(const std::string &path);
|
||||
AssetCatalogPath(const char *path);
|
||||
AssetCatalogPath(const AssetCatalogPath &other_path) = default;
|
||||
AssetCatalogPath(AssetCatalogPath &&other_path) noexcept;
|
||||
~AssetCatalogPath() = default;
|
||||
|
||||
uint64_t hash() const;
|
||||
uint64_t length() const; /* Length of the path in bytes. */
|
||||
|
||||
/** C-string representation of the path. */
|
||||
const char *c_str() const;
|
||||
const std::string &str() const;
|
||||
|
||||
/* In-class operators, because of the implicit `AssetCatalogPath(StringRef)` constructor.
|
||||
* Otherwise `string == string` could cast both sides to `AssetCatalogPath`. */
|
||||
bool operator==(const AssetCatalogPath &other_path) const;
|
||||
bool operator!=(const AssetCatalogPath &other_path) const;
|
||||
bool operator<(const AssetCatalogPath &other_path) const;
|
||||
AssetCatalogPath &operator=(const AssetCatalogPath &other_path) = default;
|
||||
AssetCatalogPath &operator=(AssetCatalogPath &&other_path) = default;
|
||||
|
||||
/** Concatenate two paths, returning the new path. */
|
||||
AssetCatalogPath operator/(const AssetCatalogPath &path_to_append) const;
|
||||
|
||||
/* False when the path is empty, true otherwise. */
|
||||
operator bool() const;
|
||||
|
||||
/**
|
||||
* Clean up the path. This ensures:
|
||||
* - Every path component is stripped of its leading/trailing spaces.
|
||||
* - Empty components (caused by double slashes or leading/trailing slashes) are removed.
|
||||
* - Invalid characters are replaced with valid ones.
|
||||
*/
|
||||
[[nodiscard]] AssetCatalogPath cleanup() const;
|
||||
|
||||
/**
|
||||
* \return true only if the given path is a parent of this catalog's path.
|
||||
* When this catalog's path is equal to the given path, return true as well.
|
||||
* In other words, this defines a weak subset.
|
||||
*
|
||||
* True: "some/path/there" is contained in "some/path" and "some".
|
||||
* False: "path/there" is not contained in "some/path/there".
|
||||
*
|
||||
* Note that non-cleaned-up paths (so for example starting or ending with a
|
||||
* slash) are not supported, and result in undefined behavior.
|
||||
*/
|
||||
bool is_contained_in(const AssetCatalogPath &other_path) const;
|
||||
|
||||
/**
|
||||
* \return the parent path, or an empty path if there is no parent.
|
||||
*/
|
||||
AssetCatalogPath parent() const;
|
||||
|
||||
/**
|
||||
* Change the initial part of the path from `from_path` to `to_path`.
|
||||
* If this path does not start with `from_path`, return an empty path as result.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* AssetCatalogPath path("some/path/to/some/catalog");
|
||||
* path.rebase("some/path", "new/base") -> "new/base/to/some/catalog"
|
||||
*/
|
||||
AssetCatalogPath rebase(const AssetCatalogPath &from_path,
|
||||
const AssetCatalogPath &to_path) const;
|
||||
|
||||
/** Call the callback function for each path component, in left-to-right order. */
|
||||
using ComponentIteratorFn = FunctionRef<void(StringRef component_name, bool is_last_component)>;
|
||||
void iterate_components(ComponentIteratorFn callback) const;
|
||||
|
||||
protected:
|
||||
/** Strip leading/trailing spaces and replace disallowed characters. */
|
||||
static std::string cleanup_component(StringRef component_name);
|
||||
};
|
||||
|
||||
/** Output the path as string. */
|
||||
std::ostream &operator<<(std::ostream &stream, const AssetCatalogPath &path_to_append);
|
||||
|
||||
} // namespace blender::bke
|
@@ -21,6 +21,7 @@
|
||||
#pragma once
|
||||
|
||||
struct Main;
|
||||
//
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
|
@@ -39,7 +39,7 @@ extern "C" {
|
||||
|
||||
/* Blender file format version. */
|
||||
#define BLENDER_FILE_VERSION BLENDER_VERSION
|
||||
#define BLENDER_FILE_SUBVERSION 31
|
||||
#define BLENDER_FILE_SUBVERSION 32
|
||||
|
||||
/* Minimum Blender version that supports reading file written with the current
|
||||
* version. Older Blender versions will test this and show a warning if the file
|
||||
|
@@ -97,6 +97,7 @@ void BKE_curvemapping_evaluate_premulRGBF(const struct CurveMapping *cumap,
|
||||
float vecout[3],
|
||||
const float vecin[3]);
|
||||
bool BKE_curvemapping_RGBA_does_something(const struct CurveMapping *cumap);
|
||||
void BKE_curvemapping_table_F(const struct CurveMapping *cumap, float **array, int *size);
|
||||
void BKE_curvemapping_table_RGBA(const struct CurveMapping *cumap, float **array, int *size);
|
||||
|
||||
/* non-const, these modify the curve */
|
||||
|
@@ -133,6 +133,9 @@ enum {
|
||||
LIB_ID_COPY_SHAPEKEY = 1 << 26,
|
||||
/** EXCEPTION! Specific deep-copy of node trees used e.g. for rendering purposes. */
|
||||
LIB_ID_COPY_NODETREE_LOCALIZE = 1 << 27,
|
||||
/** EXCEPTION! Specific handling of RB objects regarding collections differs depending whether we
|
||||
duplicate scene/collections, or objects. */
|
||||
LIB_ID_COPY_RIGID_BODY_NO_COLLECTION_HANDLING = 1 << 28,
|
||||
|
||||
/* *** Helper 'defines' gathering most common flag sets. *** */
|
||||
/** Shapekeys are not real ID's, more like local data to geometry IDs... */
|
||||
@@ -261,7 +264,8 @@ struct ID *BKE_id_copy_ex(struct Main *bmain,
|
||||
const int flag);
|
||||
struct ID *BKE_id_copy_for_duplicate(struct Main *bmain,
|
||||
struct ID *id,
|
||||
const uint duplicate_flags);
|
||||
const uint duplicate_flags,
|
||||
const int copy_flags);
|
||||
|
||||
void BKE_lib_id_swap(struct Main *bmain, struct ID *id_a, struct ID *id_b);
|
||||
void BKE_lib_id_swap_full(struct Main *bmain, struct ID *id_a, struct ID *id_b);
|
||||
|
@@ -651,9 +651,8 @@ extern void (*BKE_mesh_batch_cache_free_cb)(struct Mesh *me);
|
||||
|
||||
/* Inlines */
|
||||
|
||||
/* Instead of -1 that function uses ORIGINDEX_NONE as defined in BKE_customdata.h,
|
||||
* but I don't want to force every user of BKE_mesh.h to also include that file.
|
||||
* ~~ Sybren */
|
||||
/* NOTE(@sybren): Instead of -1 that function uses ORIGINDEX_NONE as defined in BKE_customdata.h,
|
||||
* but I don't want to force every user of BKE_mesh.h to also include that file. */
|
||||
BLI_INLINE int BKE_mesh_origindex_mface_mpoly(const int *index_mf_to_mpoly,
|
||||
const int *index_mp_to_orig,
|
||||
const int i)
|
||||
|
@@ -417,7 +417,7 @@ typedef struct bNodeTreeType {
|
||||
void (*local_sync)(struct bNodeTree *localtree, struct bNodeTree *ntree);
|
||||
void (*local_merge)(struct Main *bmain, struct bNodeTree *localtree, struct bNodeTree *ntree);
|
||||
|
||||
/* Tree update. Overrides nodetype->updatetreefunc! */
|
||||
/* Tree update. Overrides `nodetype->updatetreefunc` ! */
|
||||
void (*update)(struct bNodeTree *ntree);
|
||||
|
||||
bool (*validate_link)(struct bNodeTree *ntree, struct bNodeLink *link);
|
||||
@@ -443,7 +443,7 @@ void ntreeTypeFreeLink(const struct bNodeTreeType *nt);
|
||||
bool ntreeIsRegistered(struct bNodeTree *ntree);
|
||||
struct GHashIterator *ntreeTypeGetIterator(void);
|
||||
|
||||
/* helper macros for iterating over tree types */
|
||||
/* Helper macros for iterating over tree types. */
|
||||
#define NODE_TREE_TYPES_BEGIN(ntype) \
|
||||
{ \
|
||||
GHashIterator *__node_tree_type_iter__ = ntreeTypeGetIterator(); \
|
||||
@@ -548,7 +548,7 @@ void nodeUnregisterType(struct bNodeType *ntype);
|
||||
bool nodeTypeUndefined(struct bNode *node);
|
||||
struct GHashIterator *nodeTypeGetIterator(void);
|
||||
|
||||
/* helper macros for iterating over node types */
|
||||
/* Helper macros for iterating over node types. */
|
||||
#define NODE_TYPES_BEGIN(ntype) \
|
||||
{ \
|
||||
GHashIterator *__node_type_iter__ = nodeTypeGetIterator(); \
|
||||
@@ -574,7 +574,7 @@ const char *nodeStaticSocketType(int type, int subtype);
|
||||
const char *nodeStaticSocketInterfaceType(int type, int subtype);
|
||||
const char *nodeStaticSocketLabel(int type, int subtype);
|
||||
|
||||
/* helper macros for iterating over node types */
|
||||
/* Helper macros for iterating over node types. */
|
||||
#define NODE_SOCKET_TYPES_BEGIN(stype) \
|
||||
{ \
|
||||
GHashIterator *__node_socket_type_iter__ = nodeSocketTypeGetIterator(); \
|
||||
@@ -746,7 +746,8 @@ int BKE_node_clipboard_get_type(void);
|
||||
|
||||
/* Node Instance Hash */
|
||||
typedef struct bNodeInstanceHash {
|
||||
GHash *ghash; /* XXX should be made a direct member, GHash allocation needs to support it */
|
||||
/** XXX should be made a direct member, #GHash allocation needs to support it */
|
||||
GHash *ghash;
|
||||
} bNodeInstanceHash;
|
||||
|
||||
typedef void (*bNodeInstanceValueFP)(void *value);
|
||||
@@ -1102,6 +1103,7 @@ void BKE_nodetree_remove_layer_n(struct bNodeTree *ntree,
|
||||
#define SH_NODE_VERTEX_COLOR 706
|
||||
#define SH_NODE_OUTPUT_AOV 707
|
||||
#define SH_NODE_VECTOR_ROTATE 708
|
||||
#define SH_NODE_CURVE_FLOAT 709
|
||||
|
||||
/* custom defines options for Material node */
|
||||
// #define SH_NODE_MAT_DIFF 1
|
||||
@@ -1346,7 +1348,7 @@ void ntreeCompositCryptomatteLayerPrefix(const Scene *scene,
|
||||
const bNode *node,
|
||||
char *r_prefix,
|
||||
size_t prefix_len);
|
||||
/* Update the runtime layer names with the cryptomatte layer names of the references
|
||||
/* Update the runtime layer names with the crypto-matte layer names of the references
|
||||
* render layer or image. */
|
||||
void ntreeCompositCryptomatteUpdateLayerNames(const Scene *scene, bNode *node);
|
||||
struct CryptomatteSession *ntreeCompositCryptomatteSession(const Scene *scene, bNode *node);
|
||||
@@ -1507,7 +1509,10 @@ int ntreeTexExecTree(struct bNodeTree *ntree,
|
||||
#define GEO_NODE_POINTS_TO_VERTICES 1094
|
||||
#define GEO_NODE_CURVE_REVERSE 1095
|
||||
#define GEO_NODE_PROXIMITY 1096
|
||||
|
||||
#define GEO_NODE_CURVE_SUBDIVIDE 1097
|
||||
#define GEO_NODE_INPUT_SPLINE_LENGTH 1098
|
||||
#define GEO_NODE_CURVE_SPLINE_TYPE 1099
|
||||
#define GEO_NODE_CURVE_SET_HANDLES 1100
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
@@ -1525,6 +1530,7 @@ int ntreeTexExecTree(struct bNodeTree *ntree,
|
||||
#define FN_NODE_STRING_SUBSTRING 1212
|
||||
#define FN_NODE_INPUT_SPECIAL_CHARACTERS 1213
|
||||
#define FN_NODE_RANDOM_VALUE 1214
|
||||
#define FN_NODE_ROTATE_EULER 1215
|
||||
|
||||
/** \} */
|
||||
|
||||
|
@@ -85,6 +85,7 @@ set(SRC
|
||||
intern/armature_update.c
|
||||
intern/asset.cc
|
||||
intern/asset_catalog.cc
|
||||
intern/asset_catalog_path.cc
|
||||
intern/asset_library.cc
|
||||
intern/attribute.c
|
||||
intern/attribute_access.cc
|
||||
@@ -306,6 +307,7 @@ set(SRC
|
||||
BKE_armature.hh
|
||||
BKE_asset.h
|
||||
BKE_asset_catalog.hh
|
||||
BKE_asset_catalog_path.hh
|
||||
BKE_asset_library.h
|
||||
BKE_asset_library.hh
|
||||
BKE_attribute.h
|
||||
@@ -789,6 +791,7 @@ if(WITH_GTESTS)
|
||||
intern/action_test.cc
|
||||
intern/armature_test.cc
|
||||
intern/asset_catalog_test.cc
|
||||
intern/asset_catalog_path_test.cc
|
||||
intern/asset_library_test.cc
|
||||
intern/asset_test.cc
|
||||
intern/cryptomatte_test.cc
|
||||
|
@@ -38,7 +38,6 @@
|
||||
|
||||
namespace blender::bke {
|
||||
|
||||
const char AssetCatalogService::PATH_SEPARATOR = '/';
|
||||
const CatalogFilePath AssetCatalogService::DEFAULT_CATALOG_FILENAME = "blender_assets.cats.txt";
|
||||
|
||||
/* For now this is the only version of the catalog definition files that is supported.
|
||||
@@ -66,16 +65,16 @@ bool AssetCatalogService::is_empty() const
|
||||
return catalogs_.is_empty();
|
||||
}
|
||||
|
||||
AssetCatalog *AssetCatalogService::find_catalog(CatalogID catalog_id)
|
||||
AssetCatalog *AssetCatalogService::find_catalog(CatalogID catalog_id) const
|
||||
{
|
||||
std::unique_ptr<AssetCatalog> *catalog_uptr_ptr = this->catalogs_.lookup_ptr(catalog_id);
|
||||
const std::unique_ptr<AssetCatalog> *catalog_uptr_ptr = this->catalogs_.lookup_ptr(catalog_id);
|
||||
if (catalog_uptr_ptr == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return catalog_uptr_ptr->get();
|
||||
}
|
||||
|
||||
AssetCatalog *AssetCatalogService::find_catalog_by_path(const CatalogPath &path) const
|
||||
AssetCatalog *AssetCatalogService::find_catalog_by_path(const AssetCatalogPath &path) const
|
||||
{
|
||||
for (const auto &catalog : catalogs_.values()) {
|
||||
if (catalog->path == path) {
|
||||
@@ -86,6 +85,33 @@ AssetCatalog *AssetCatalogService::find_catalog_by_path(const CatalogPath &path)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AssetCatalogFilter AssetCatalogService::create_catalog_filter(
|
||||
const CatalogID active_catalog_id) const
|
||||
{
|
||||
Set<CatalogID> matching_catalog_ids;
|
||||
matching_catalog_ids.add(active_catalog_id);
|
||||
|
||||
const AssetCatalog *active_catalog = find_catalog(active_catalog_id);
|
||||
if (!active_catalog) {
|
||||
/* If the UUID is unknown (i.e. not mapped to an actual Catalog), it is impossible to determine
|
||||
* its children. The filter can still work on the given UUID. */
|
||||
return AssetCatalogFilter(std::move(matching_catalog_ids));
|
||||
}
|
||||
|
||||
/* This cannot just iterate over tree items to get all the required data, because tree items only
|
||||
* represent single UUIDs. It could be used to get the main UUIDs of the children, though, and
|
||||
* then only do an exact match on the path (instead of the more complex `is_contained_in()`
|
||||
* call). Without an extra indexed-by-path acceleration structure, this is still going to require
|
||||
* a linear search, though. */
|
||||
for (const auto &catalog_uptr : this->catalogs_.values()) {
|
||||
if (catalog_uptr->path.is_contained_in(active_catalog->path)) {
|
||||
matching_catalog_ids.add(catalog_uptr->catalog_id);
|
||||
}
|
||||
}
|
||||
|
||||
return AssetCatalogFilter(std::move(matching_catalog_ids));
|
||||
}
|
||||
|
||||
void AssetCatalogService::delete_catalog(CatalogID catalog_id)
|
||||
{
|
||||
std::unique_ptr<AssetCatalog> *catalog_uptr_ptr = this->catalogs_.lookup_ptr(catalog_id);
|
||||
@@ -108,25 +134,25 @@ void AssetCatalogService::delete_catalog(CatalogID catalog_id)
|
||||
}
|
||||
|
||||
void AssetCatalogService::update_catalog_path(CatalogID catalog_id,
|
||||
const CatalogPath &new_catalog_path)
|
||||
const AssetCatalogPath &new_catalog_path)
|
||||
{
|
||||
AssetCatalog *renamed_cat = this->find_catalog(catalog_id);
|
||||
const CatalogPath old_cat_path = renamed_cat->path;
|
||||
const AssetCatalogPath old_cat_path = renamed_cat->path;
|
||||
|
||||
for (auto &catalog_uptr : catalogs_.values()) {
|
||||
AssetCatalog *cat = catalog_uptr.get();
|
||||
if (!cat->is_contained_in(old_cat_path)) {
|
||||
|
||||
const AssetCatalogPath new_path = cat->path.rebase(old_cat_path, new_catalog_path);
|
||||
if (!new_path) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const CatalogPath path_suffix = cat->path.substr(old_cat_path.length());
|
||||
cat->path = new_catalog_path + path_suffix;
|
||||
cat->path = new_path;
|
||||
}
|
||||
|
||||
this->rebuild_tree();
|
||||
}
|
||||
|
||||
AssetCatalog *AssetCatalogService::create_catalog(const CatalogPath &catalog_path)
|
||||
AssetCatalog *AssetCatalogService::create_catalog(const AssetCatalogPath &catalog_path)
|
||||
{
|
||||
std::unique_ptr<AssetCatalog> catalog = AssetCatalog::from_path(catalog_path);
|
||||
|
||||
@@ -145,11 +171,8 @@ AssetCatalog *AssetCatalogService::create_catalog(const CatalogPath &catalog_pat
|
||||
catalog_definition_file_->add_new(catalog_ptr);
|
||||
}
|
||||
|
||||
/* The tree may not exist; this happens when no catalog definition file has been loaded yet. When
|
||||
* the tree is created any in-memory catalogs will be added, so it doesn't need to happen now. */
|
||||
if (catalog_tree_) {
|
||||
catalog_tree_->insert_item(*catalog_ptr);
|
||||
}
|
||||
BLI_assert_msg(catalog_tree_, "An Asset Catalog tree should always exist.");
|
||||
catalog_tree_->insert_item(*catalog_ptr);
|
||||
|
||||
return catalog_ptr;
|
||||
}
|
||||
@@ -189,7 +212,7 @@ void AssetCatalogService::load_from_disk(const CatalogFilePath &file_or_director
|
||||
|
||||
/* TODO: Should there be a sanitize step? E.g. to remove catalogs with identical paths? */
|
||||
|
||||
catalog_tree_ = read_into_tree();
|
||||
rebuild_tree();
|
||||
}
|
||||
|
||||
void AssetCatalogService::load_directory_recursive(const CatalogFilePath &directory_path)
|
||||
@@ -319,8 +342,7 @@ CatalogFilePath AssetCatalogService::find_suitable_cdf_path_for_writing(
|
||||
/* - There's no definition file next to the .blend file.
|
||||
* -> Ask the asset library API for an appropriate location. */
|
||||
char suitable_root_path[PATH_MAX];
|
||||
BKE_asset_library_find_suitable_root_path_from_path(blend_file_path.c_str(),
|
||||
suitable_root_path);
|
||||
BKE_asset_library_find_suitable_root_path_from_path(blend_file_path.c_str(), suitable_root_path);
|
||||
char asset_lib_cdf_path[PATH_MAX];
|
||||
BLI_path_join(asset_lib_cdf_path,
|
||||
sizeof(asset_lib_cdf_path),
|
||||
@@ -358,9 +380,48 @@ std::unique_ptr<AssetCatalogTree> AssetCatalogService::read_into_tree()
|
||||
|
||||
void AssetCatalogService::rebuild_tree()
|
||||
{
|
||||
create_missing_catalogs();
|
||||
this->catalog_tree_ = read_into_tree();
|
||||
}
|
||||
|
||||
void AssetCatalogService::create_missing_catalogs()
|
||||
{
|
||||
/* Construct an ordered set of paths to check, so that parents are ordered before children. */
|
||||
std::set<AssetCatalogPath> paths_to_check;
|
||||
for (auto &catalog : catalogs_.values()) {
|
||||
paths_to_check.insert(catalog->path);
|
||||
}
|
||||
|
||||
std::set<AssetCatalogPath> seen_paths;
|
||||
/* The empty parent should never be created, so always be considered "seen". */
|
||||
seen_paths.insert(AssetCatalogPath(""));
|
||||
|
||||
/* Find and create missing direct parents (so ignoring parents-of-parents). */
|
||||
while (!paths_to_check.empty()) {
|
||||
/* Pop the first path of the queue. */
|
||||
const AssetCatalogPath path = *paths_to_check.begin();
|
||||
paths_to_check.erase(paths_to_check.begin());
|
||||
|
||||
if (seen_paths.find(path) != seen_paths.end()) {
|
||||
/* This path has been seen already, so it can be ignored. */
|
||||
continue;
|
||||
}
|
||||
seen_paths.insert(path);
|
||||
|
||||
const AssetCatalogPath parent_path = path.parent();
|
||||
if (seen_paths.find(parent_path) != seen_paths.end()) {
|
||||
/* The parent exists, continue to the next path. */
|
||||
continue;
|
||||
}
|
||||
|
||||
/* The parent doesn't exist, so create it and queue it up for checking its parent. */
|
||||
create_catalog(parent_path);
|
||||
paths_to_check.insert(parent_path);
|
||||
}
|
||||
|
||||
/* TODO(Sybren): bind the newly created catalogs to a CDF, if we know about it. */
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
AssetCatalogTreeItem::AssetCatalogTreeItem(StringRef name,
|
||||
@@ -380,11 +441,11 @@ StringRef AssetCatalogTreeItem::get_name() const
|
||||
return name_;
|
||||
}
|
||||
|
||||
CatalogPath AssetCatalogTreeItem::catalog_path() const
|
||||
AssetCatalogPath AssetCatalogTreeItem::catalog_path() const
|
||||
{
|
||||
std::string current_path = name_;
|
||||
AssetCatalogPath current_path = name_;
|
||||
for (const AssetCatalogTreeItem *parent = parent_; parent; parent = parent->parent_) {
|
||||
current_path = parent->name_ + AssetCatalogService::PATH_SEPARATOR + current_path;
|
||||
current_path = AssetCatalogPath(parent->name_) / current_path;
|
||||
}
|
||||
return current_path;
|
||||
}
|
||||
@@ -405,32 +466,6 @@ bool AssetCatalogTreeItem::has_children() const
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Iterate over path components, calling \a callback for each component. E.g. "just/some/path"
|
||||
* iterates over "just", then "some" then "path".
|
||||
*/
|
||||
static void iterate_over_catalog_path_components(
|
||||
const CatalogPath &path,
|
||||
FunctionRef<void(StringRef component_name, bool is_last_component)> callback)
|
||||
{
|
||||
const char *next_slash_ptr;
|
||||
|
||||
for (const char *path_component = path.data(); path_component && path_component[0];
|
||||
/* Jump to one after the next slash if there is any. */
|
||||
path_component = next_slash_ptr ? next_slash_ptr + 1 : nullptr) {
|
||||
next_slash_ptr = BLI_path_slash_find(path_component);
|
||||
|
||||
const bool is_last_component = next_slash_ptr == nullptr;
|
||||
/* Note that this won't be null terminated. */
|
||||
const StringRef component_name = is_last_component ?
|
||||
path_component :
|
||||
StringRef(path_component,
|
||||
next_slash_ptr - path_component);
|
||||
|
||||
callback(component_name, is_last_component);
|
||||
}
|
||||
}
|
||||
|
||||
void AssetCatalogTree::insert_item(const AssetCatalog &catalog)
|
||||
{
|
||||
const AssetCatalogTreeItem *parent = nullptr;
|
||||
@@ -438,30 +473,29 @@ void AssetCatalogTree::insert_item(const AssetCatalog &catalog)
|
||||
* added to (if not there yet). */
|
||||
AssetCatalogTreeItem::ChildMap *current_item_children = &root_items_;
|
||||
|
||||
BLI_assert_msg(!ELEM(catalog.path[0], '/', '\\'),
|
||||
BLI_assert_msg(!ELEM(catalog.path.str()[0], '/', '\\'),
|
||||
"Malformed catalog path; should not start with a separator");
|
||||
|
||||
const CatalogID nil_id{};
|
||||
|
||||
iterate_over_catalog_path_components(
|
||||
catalog.path, [&](StringRef component_name, const bool is_last_component) {
|
||||
/* Insert new tree element - if no matching one is there yet! */
|
||||
auto [key_and_item, was_inserted] = current_item_children->emplace(
|
||||
component_name,
|
||||
AssetCatalogTreeItem(
|
||||
component_name, is_last_component ? catalog.catalog_id : nil_id, parent));
|
||||
AssetCatalogTreeItem &item = key_and_item->second;
|
||||
catalog.path.iterate_components([&](StringRef component_name, const bool is_last_component) {
|
||||
/* Insert new tree element - if no matching one is there yet! */
|
||||
auto [key_and_item, was_inserted] = current_item_children->emplace(
|
||||
component_name,
|
||||
AssetCatalogTreeItem(
|
||||
component_name, is_last_component ? catalog.catalog_id : nil_id, parent));
|
||||
AssetCatalogTreeItem &item = key_and_item->second;
|
||||
|
||||
/* If full path of this catalog already exists as parent path of a previously read catalog,
|
||||
* we can ensure this tree item's UUID is set here. */
|
||||
if (is_last_component && BLI_uuid_is_nil(item.catalog_id_)) {
|
||||
item.catalog_id_ = catalog.catalog_id;
|
||||
}
|
||||
/* If full path of this catalog already exists as parent path of a previously read catalog,
|
||||
* we can ensure this tree item's UUID is set here. */
|
||||
if (is_last_component && BLI_uuid_is_nil(item.catalog_id_)) {
|
||||
item.catalog_id_ = catalog.catalog_id;
|
||||
}
|
||||
|
||||
/* Walk further into the path (no matter if a new item was created or not). */
|
||||
parent = &item;
|
||||
current_item_children = &item.children_;
|
||||
});
|
||||
/* Walk further into the path (no matter if a new item was created or not). */
|
||||
parent = &item;
|
||||
current_item_children = &item.children_;
|
||||
});
|
||||
}
|
||||
|
||||
void AssetCatalogTree::foreach_item(AssetCatalogTreeItem::ItemIterFn callback)
|
||||
@@ -592,7 +626,7 @@ std::unique_ptr<AssetCatalog> AssetCatalogDefinitionFile::parse_catalog_line(con
|
||||
const StringRef path_and_simple_name = line.substr(first_delim + 1);
|
||||
const int64_t second_delim = path_and_simple_name.find_first_of(delim);
|
||||
|
||||
CatalogPath catalog_path;
|
||||
std::string path_in_file;
|
||||
std::string simple_name;
|
||||
if (second_delim == 0) {
|
||||
/* Delimiter as first character means there is no path. These lines are to be ignored. */
|
||||
@@ -601,16 +635,16 @@ std::unique_ptr<AssetCatalog> AssetCatalogDefinitionFile::parse_catalog_line(con
|
||||
|
||||
if (second_delim == StringRef::not_found) {
|
||||
/* No delimiter means no simple name, just treat it as all "path". */
|
||||
catalog_path = path_and_simple_name;
|
||||
path_in_file = path_and_simple_name;
|
||||
simple_name = "";
|
||||
}
|
||||
else {
|
||||
catalog_path = path_and_simple_name.substr(0, second_delim);
|
||||
path_in_file = path_and_simple_name.substr(0, second_delim);
|
||||
simple_name = path_and_simple_name.substr(second_delim + 1).trim();
|
||||
}
|
||||
|
||||
catalog_path = AssetCatalog::cleanup_path(catalog_path);
|
||||
return std::make_unique<AssetCatalog>(catalog_id, catalog_path, simple_name);
|
||||
AssetCatalogPath catalog_path = path_in_file;
|
||||
return std::make_unique<AssetCatalog>(catalog_id, catalog_path.cleanup(), simple_name);
|
||||
}
|
||||
|
||||
bool AssetCatalogDefinitionFile::write_to_disk() const
|
||||
@@ -716,25 +750,25 @@ bool AssetCatalogDefinitionFile::ensure_directory_exists(
|
||||
}
|
||||
|
||||
AssetCatalog::AssetCatalog(const CatalogID catalog_id,
|
||||
const CatalogPath &path,
|
||||
const AssetCatalogPath &path,
|
||||
const std::string &simple_name)
|
||||
: catalog_id(catalog_id), path(path), simple_name(simple_name)
|
||||
{
|
||||
}
|
||||
|
||||
std::unique_ptr<AssetCatalog> AssetCatalog::from_path(const CatalogPath &path)
|
||||
std::unique_ptr<AssetCatalog> AssetCatalog::from_path(const AssetCatalogPath &path)
|
||||
{
|
||||
const CatalogPath clean_path = cleanup_path(path);
|
||||
const AssetCatalogPath clean_path = path.cleanup();
|
||||
const CatalogID cat_id = BLI_uuid_generate_random();
|
||||
const std::string simple_name = sensible_simple_name_for_path(clean_path);
|
||||
auto catalog = std::make_unique<AssetCatalog>(cat_id, clean_path, simple_name);
|
||||
return catalog;
|
||||
}
|
||||
|
||||
std::string AssetCatalog::sensible_simple_name_for_path(const CatalogPath &path)
|
||||
std::string AssetCatalog::sensible_simple_name_for_path(const AssetCatalogPath &path)
|
||||
{
|
||||
std::string name = path;
|
||||
std::replace(name.begin(), name.end(), AssetCatalogService::PATH_SEPARATOR, '-');
|
||||
std::string name = path.str();
|
||||
std::replace(name.begin(), name.end(), AssetCatalogPath::SEPARATOR, '-');
|
||||
if (name.length() < MAX_NAME - 1) {
|
||||
return name;
|
||||
}
|
||||
@@ -744,33 +778,14 @@ std::string AssetCatalog::sensible_simple_name_for_path(const CatalogPath &path)
|
||||
return "..." + name.substr(name.length() - 60);
|
||||
}
|
||||
|
||||
CatalogPath AssetCatalog::cleanup_path(const CatalogPath &path)
|
||||
AssetCatalogFilter::AssetCatalogFilter(Set<CatalogID> &&matching_catalog_ids)
|
||||
: matching_catalog_ids(std::move(matching_catalog_ids))
|
||||
{
|
||||
/* TODO(@sybren): maybe go over each element of the path, and trim those? */
|
||||
CatalogPath clean_path = StringRef(path).trim().trim(AssetCatalogService::PATH_SEPARATOR).trim();
|
||||
return clean_path;
|
||||
}
|
||||
|
||||
bool AssetCatalog::is_contained_in(const CatalogPath &other_path) const
|
||||
bool AssetCatalogFilter::contains(const CatalogID asset_catalog_id) const
|
||||
{
|
||||
if (other_path.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this->path == other_path) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* To be a child path of 'other_path', our path must be at least a separator and another
|
||||
* character longer. */
|
||||
if (this->path.length() < other_path.length() + 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const StringRef this_path(this->path);
|
||||
const bool prefix_ok = this_path.startswith(other_path);
|
||||
const char next_char = this_path[other_path.length()];
|
||||
return prefix_ok && next_char == AssetCatalogService::PATH_SEPARATOR;
|
||||
return matching_catalog_ids.contains(asset_catalog_id);
|
||||
}
|
||||
|
||||
} // namespace blender::bke
|
||||
|
228
source/blender/blenkernel/intern/asset_catalog_path.cc
Normal file
228
source/blender/blenkernel/intern/asset_catalog_path.cc
Normal file
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/** \file
|
||||
* \ingroup bke
|
||||
*/
|
||||
|
||||
#include "BKE_asset_catalog_path.hh"
|
||||
|
||||
#include "BLI_path_util.h"
|
||||
|
||||
namespace blender::bke {
|
||||
|
||||
const char AssetCatalogPath::SEPARATOR = '/';
|
||||
|
||||
AssetCatalogPath::AssetCatalogPath(const std::string &path) : path_(path)
|
||||
{
|
||||
}
|
||||
|
||||
AssetCatalogPath::AssetCatalogPath(StringRef path) : path_(path)
|
||||
{
|
||||
}
|
||||
|
||||
AssetCatalogPath::AssetCatalogPath(const char *path) : path_(path)
|
||||
{
|
||||
}
|
||||
|
||||
AssetCatalogPath::AssetCatalogPath(AssetCatalogPath &&other_path) noexcept
|
||||
: path_(std::move(other_path.path_))
|
||||
{
|
||||
}
|
||||
|
||||
uint64_t AssetCatalogPath::hash() const
|
||||
{
|
||||
std::hash<std::string> hasher{};
|
||||
return hasher(this->path_);
|
||||
}
|
||||
|
||||
uint64_t AssetCatalogPath::length() const
|
||||
{
|
||||
return this->path_.length();
|
||||
}
|
||||
|
||||
const char *AssetCatalogPath::c_str() const
|
||||
{
|
||||
return this->path_.c_str();
|
||||
}
|
||||
|
||||
const std::string &AssetCatalogPath::str() const
|
||||
{
|
||||
return this->path_;
|
||||
}
|
||||
|
||||
/* In-class operators, because of the implicit `AssetCatalogPath(StringRef)` constructor.
|
||||
* Otherwise `string == string` could cast both sides to `AssetCatalogPath`. */
|
||||
bool AssetCatalogPath::operator==(const AssetCatalogPath &other_path) const
|
||||
{
|
||||
return this->path_ == other_path.path_;
|
||||
}
|
||||
|
||||
bool AssetCatalogPath::operator!=(const AssetCatalogPath &other_path) const
|
||||
{
|
||||
return !(*this == other_path);
|
||||
}
|
||||
|
||||
bool AssetCatalogPath::operator<(const AssetCatalogPath &other_path) const
|
||||
{
|
||||
return this->path_ < other_path.path_;
|
||||
}
|
||||
|
||||
AssetCatalogPath AssetCatalogPath::operator/(const AssetCatalogPath &path_to_append) const
|
||||
{
|
||||
/* `"" / "path"` or `"path" / ""` should just result in `"path"` */
|
||||
if (!*this) {
|
||||
return path_to_append;
|
||||
}
|
||||
if (!path_to_append) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::stringstream new_path;
|
||||
new_path << this->path_ << SEPARATOR << path_to_append.path_;
|
||||
return AssetCatalogPath(new_path.str());
|
||||
}
|
||||
|
||||
AssetCatalogPath::operator bool() const
|
||||
{
|
||||
return !this->path_.empty();
|
||||
}
|
||||
|
||||
std::ostream &operator<<(std::ostream &stream, const AssetCatalogPath &path_to_append)
|
||||
{
|
||||
stream << path_to_append.path_;
|
||||
return stream;
|
||||
}
|
||||
|
||||
AssetCatalogPath AssetCatalogPath::cleanup() const
|
||||
{
|
||||
std::stringstream clean_components;
|
||||
bool first_component_seen = false;
|
||||
|
||||
this->iterate_components([&clean_components, &first_component_seen](StringRef component_name,
|
||||
bool /*is_last_component*/) {
|
||||
const std::string clean_component = cleanup_component(component_name);
|
||||
|
||||
if (clean_component.empty()) {
|
||||
/* These are caused by leading, trailing, or double slashes. */
|
||||
return;
|
||||
}
|
||||
|
||||
/* If a previous path component has been streamed already, we need a path separator. This
|
||||
* cannot use the `is_last_component` boolean, because the last component might be skipped due
|
||||
* to the condition above. */
|
||||
if (first_component_seen) {
|
||||
clean_components << SEPARATOR;
|
||||
}
|
||||
first_component_seen = true;
|
||||
|
||||
clean_components << clean_component;
|
||||
});
|
||||
|
||||
return AssetCatalogPath(clean_components.str());
|
||||
}
|
||||
|
||||
std::string AssetCatalogPath::cleanup_component(StringRef component)
|
||||
{
|
||||
std::string cleaned = component.trim();
|
||||
/* Replace colons with something else, as those are used in the CDF file as delimiter. */
|
||||
std::replace(cleaned.begin(), cleaned.end(), ':', '-');
|
||||
return cleaned;
|
||||
}
|
||||
|
||||
bool AssetCatalogPath::is_contained_in(const AssetCatalogPath &other_path) const
|
||||
{
|
||||
if (!other_path) {
|
||||
/* The empty path contains all other paths. */
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this->path_ == other_path.path_) {
|
||||
/* Weak is-in relation: equal paths contain each other. */
|
||||
return true;
|
||||
}
|
||||
|
||||
/* To be a child path of 'other_path', our path must be at least a separator and another
|
||||
* character longer. */
|
||||
if (this->length() < other_path.length() + 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Create StringRef to be able to use .startswith(). */
|
||||
const StringRef this_path(this->path_);
|
||||
const bool prefix_ok = this_path.startswith(other_path.path_);
|
||||
const char next_char = this_path[other_path.length()];
|
||||
return prefix_ok && next_char == SEPARATOR;
|
||||
}
|
||||
|
||||
AssetCatalogPath AssetCatalogPath::parent() const
|
||||
{
|
||||
if (!*this) {
|
||||
return AssetCatalogPath("");
|
||||
}
|
||||
std::string::size_type last_sep_index = this->path_.rfind(SEPARATOR);
|
||||
if (last_sep_index == std::string::npos) {
|
||||
return AssetCatalogPath("");
|
||||
}
|
||||
return AssetCatalogPath(this->path_.substr(0, last_sep_index));
|
||||
}
|
||||
|
||||
void AssetCatalogPath::iterate_components(ComponentIteratorFn callback) const
|
||||
{
|
||||
const char *next_slash_ptr;
|
||||
|
||||
for (const char *path_component = this->path_.data(); path_component && path_component[0];
|
||||
/* Jump to one after the next slash if there is any. */
|
||||
path_component = next_slash_ptr ? next_slash_ptr + 1 : nullptr) {
|
||||
next_slash_ptr = BLI_path_slash_find(path_component);
|
||||
|
||||
const bool is_last_component = next_slash_ptr == nullptr;
|
||||
/* Note that this won't be null terminated. */
|
||||
const StringRef component_name = is_last_component ?
|
||||
path_component :
|
||||
StringRef(path_component,
|
||||
next_slash_ptr - path_component);
|
||||
|
||||
callback(component_name, is_last_component);
|
||||
}
|
||||
}
|
||||
|
||||
AssetCatalogPath AssetCatalogPath::rebase(const AssetCatalogPath &from_path,
|
||||
const AssetCatalogPath &to_path) const
|
||||
{
|
||||
if (!from_path) {
|
||||
if (!to_path) {
|
||||
return AssetCatalogPath("");
|
||||
}
|
||||
return to_path / *this;
|
||||
}
|
||||
|
||||
if (!this->is_contained_in(from_path)) {
|
||||
return AssetCatalogPath("");
|
||||
}
|
||||
|
||||
if (*this == from_path) {
|
||||
/* Early return, because otherwise the length+1 below is going to cause problems. */
|
||||
return to_path;
|
||||
}
|
||||
|
||||
/* When from_path = "test", we need to skip "test/" to get the rest of the path, hence the +1. */
|
||||
const StringRef suffix = StringRef(this->path_).substr(from_path.length() + 1);
|
||||
const AssetCatalogPath path_suffix(suffix);
|
||||
return to_path / path_suffix;
|
||||
}
|
||||
|
||||
} // namespace blender::bke
|
251
source/blender/blenkernel/intern/asset_catalog_path_test.cc
Normal file
251
source/blender/blenkernel/intern/asset_catalog_path_test.cc
Normal file
@@ -0,0 +1,251 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
* The Original Code is Copyright (C) 2020 Blender Foundation
|
||||
* All rights reserved.
|
||||
*/
|
||||
|
||||
#include "BKE_asset_catalog_path.hh"
|
||||
|
||||
#include "BLI_set.hh"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
|
||||
#include "testing/testing.h"
|
||||
|
||||
namespace blender::bke::tests {
|
||||
|
||||
TEST(AssetCatalogPathTest, construction)
|
||||
{
|
||||
AssetCatalogPath from_char_literal("the/path");
|
||||
|
||||
const std::string str_const = "the/path";
|
||||
AssetCatalogPath from_string_constant(str_const);
|
||||
|
||||
std::string str_variable = "the/path";
|
||||
AssetCatalogPath from_string_variable(str_variable);
|
||||
|
||||
std::string long_string = "this is a long/string/with/a/path in the middle";
|
||||
StringRef long_string_ref(long_string);
|
||||
StringRef middle_bit = long_string_ref.substr(10, 23);
|
||||
AssetCatalogPath from_string_ref(middle_bit);
|
||||
EXPECT_EQ(from_string_ref, "long/string/with/a/path");
|
||||
}
|
||||
|
||||
TEST(AssetCatalogPathTest, length)
|
||||
{
|
||||
const AssetCatalogPath one("1");
|
||||
EXPECT_EQ(1, one.length());
|
||||
|
||||
const AssetCatalogPath empty("");
|
||||
EXPECT_EQ(0, empty.length());
|
||||
|
||||
const AssetCatalogPath utf8("some/родитель");
|
||||
EXPECT_EQ(21, utf8.length()) << "13 characters should be 21 bytes.";
|
||||
}
|
||||
|
||||
TEST(AssetCatalogPathTest, comparison_operators)
|
||||
{
|
||||
const AssetCatalogPath empty("");
|
||||
const AssetCatalogPath the_path("the/path");
|
||||
const AssetCatalogPath the_path_child("the/path/child");
|
||||
const AssetCatalogPath unrelated_path("unrelated/path");
|
||||
const AssetCatalogPath other_instance_same_path("the/path");
|
||||
|
||||
EXPECT_LT(empty, the_path);
|
||||
EXPECT_LT(the_path, the_path_child);
|
||||
EXPECT_LT(the_path, unrelated_path);
|
||||
|
||||
EXPECT_EQ(empty, empty) << "Identical empty instances should compare equal.";
|
||||
EXPECT_EQ(empty, "") << "Comparison to empty string should be possible.";
|
||||
EXPECT_EQ(the_path, the_path) << "Identical non-empty instances should compare equal.";
|
||||
EXPECT_EQ(the_path, "the/path") << "Comparison to string should be possible.";
|
||||
EXPECT_EQ(the_path, other_instance_same_path)
|
||||
<< "Different instances with equal path should compare equal.";
|
||||
|
||||
EXPECT_NE(the_path, the_path_child);
|
||||
EXPECT_NE(the_path, unrelated_path);
|
||||
EXPECT_NE(the_path, empty);
|
||||
|
||||
EXPECT_FALSE(empty);
|
||||
EXPECT_TRUE(the_path);
|
||||
}
|
||||
|
||||
TEST(AssetCatalogPathTest, move_semantics)
|
||||
{
|
||||
AssetCatalogPath source_path("source/path");
|
||||
EXPECT_TRUE(source_path);
|
||||
|
||||
AssetCatalogPath dest_path = std::move(source_path);
|
||||
EXPECT_FALSE(source_path);
|
||||
EXPECT_TRUE(dest_path);
|
||||
}
|
||||
|
||||
TEST(AssetCatalogPathTest, concatenation)
|
||||
{
|
||||
AssetCatalogPath some_parent("some/родитель");
|
||||
AssetCatalogPath child = some_parent / "ребенок";
|
||||
|
||||
EXPECT_EQ(some_parent, "some/родитель")
|
||||
<< "Appending a child path should not modify the parent.";
|
||||
EXPECT_EQ(child, "some/родитель/ребенок");
|
||||
|
||||
AssetCatalogPath appended_compound_path = some_parent / "ребенок/внук";
|
||||
EXPECT_EQ(appended_compound_path, "some/родитель/ребенок/внук");
|
||||
|
||||
AssetCatalogPath empty("");
|
||||
AssetCatalogPath child_of_the_void = empty / "child";
|
||||
EXPECT_EQ(child_of_the_void, "child")
|
||||
<< "Appending to an empty path should not create an initial slash.";
|
||||
|
||||
AssetCatalogPath parent_of_the_void = some_parent / empty;
|
||||
EXPECT_EQ(parent_of_the_void, "some/родитель")
|
||||
<< "Prepending to an empty path should not create a trailing slash.";
|
||||
|
||||
std::string subpath = "child";
|
||||
AssetCatalogPath concatenated_with_string = some_parent / subpath;
|
||||
EXPECT_EQ(concatenated_with_string, "some/родитель/child");
|
||||
}
|
||||
|
||||
TEST(AssetCatalogPathTest, hashable)
|
||||
{
|
||||
AssetCatalogPath path("heyyyyy");
|
||||
|
||||
std::set<AssetCatalogPath> path_std_set;
|
||||
path_std_set.insert(path);
|
||||
|
||||
blender::Set<AssetCatalogPath> path_blender_set;
|
||||
path_blender_set.add(path);
|
||||
}
|
||||
|
||||
TEST(AssetCatalogPathTest, stream_operator)
|
||||
{
|
||||
AssetCatalogPath path("путь/в/Пермь");
|
||||
std::stringstream sstream;
|
||||
sstream << path;
|
||||
EXPECT_EQ("путь/в/Пермь", sstream.str());
|
||||
}
|
||||
|
||||
TEST(AssetCatalogPathTest, is_contained_in)
|
||||
{
|
||||
const AssetCatalogPath catpath("simple/path/child");
|
||||
EXPECT_FALSE(catpath.is_contained_in("unrelated"));
|
||||
EXPECT_FALSE(catpath.is_contained_in("sim"));
|
||||
EXPECT_FALSE(catpath.is_contained_in("simple/pathx"));
|
||||
EXPECT_FALSE(catpath.is_contained_in("simple/path/c"));
|
||||
EXPECT_FALSE(catpath.is_contained_in("simple/path/child/grandchild"));
|
||||
EXPECT_FALSE(catpath.is_contained_in("simple/path/"))
|
||||
<< "Non-normalized paths are not expected to work.";
|
||||
|
||||
EXPECT_TRUE(catpath.is_contained_in(""));
|
||||
EXPECT_TRUE(catpath.is_contained_in("simple"));
|
||||
EXPECT_TRUE(catpath.is_contained_in("simple/path"));
|
||||
|
||||
/* Test with some UTF8 non-ASCII characters. */
|
||||
AssetCatalogPath some_parent("some/родитель");
|
||||
AssetCatalogPath child = some_parent / "ребенок";
|
||||
|
||||
EXPECT_TRUE(child.is_contained_in(some_parent));
|
||||
EXPECT_TRUE(child.is_contained_in("some"));
|
||||
|
||||
AssetCatalogPath appended_compound_path = some_parent / "ребенок/внук";
|
||||
EXPECT_TRUE(appended_compound_path.is_contained_in(some_parent));
|
||||
EXPECT_TRUE(appended_compound_path.is_contained_in(child));
|
||||
|
||||
/* Test "going up" directory-style. */
|
||||
AssetCatalogPath child_with_dotdot = some_parent / "../../other/hierarchy/part";
|
||||
EXPECT_TRUE(child_with_dotdot.is_contained_in(some_parent))
|
||||
<< "dotdot path components should have no meaning";
|
||||
}
|
||||
|
||||
TEST(AssetCatalogPathTest, cleanup)
|
||||
{
|
||||
AssetCatalogPath ugly_path("/ some / родитель / ");
|
||||
AssetCatalogPath clean_path = ugly_path.cleanup();
|
||||
|
||||
EXPECT_EQ(AssetCatalogPath("/ some / родитель / "), ugly_path)
|
||||
<< "cleanup should not modify the path instance itself";
|
||||
|
||||
EXPECT_EQ(AssetCatalogPath("some/родитель"), clean_path);
|
||||
|
||||
AssetCatalogPath double_slashed("some//родитель");
|
||||
EXPECT_EQ(AssetCatalogPath("some/родитель"), double_slashed.cleanup());
|
||||
|
||||
AssetCatalogPath with_colons("some/key:subkey=value/path");
|
||||
EXPECT_EQ(AssetCatalogPath("some/key-subkey=value/path"), with_colons.cleanup());
|
||||
}
|
||||
|
||||
TEST(AssetCatalogPathTest, iterate_components)
|
||||
{
|
||||
AssetCatalogPath path("путь/в/Пермь");
|
||||
Vector<std::pair<std::string, bool>> seen_components;
|
||||
|
||||
path.iterate_components([&seen_components](StringRef component_name, bool is_last_component) {
|
||||
std::pair<std::string, bool> parameter_pair = std::make_pair<std::string, bool>(
|
||||
component_name, bool(is_last_component));
|
||||
seen_components.append(parameter_pair);
|
||||
});
|
||||
|
||||
ASSERT_EQ(3, seen_components.size());
|
||||
|
||||
EXPECT_EQ("путь", seen_components[0].first);
|
||||
EXPECT_EQ("в", seen_components[1].first);
|
||||
EXPECT_EQ("Пермь", seen_components[2].first);
|
||||
|
||||
EXPECT_FALSE(seen_components[0].second);
|
||||
EXPECT_FALSE(seen_components[1].second);
|
||||
EXPECT_TRUE(seen_components[2].second);
|
||||
}
|
||||
|
||||
TEST(AssetCatalogPathTest, rebase)
|
||||
{
|
||||
AssetCatalogPath path("some/path/to/some/catalog");
|
||||
EXPECT_EQ(path.rebase("some/path", "new/base"), "new/base/to/some/catalog");
|
||||
EXPECT_EQ(path.rebase("", "new/base"), "new/base/some/path/to/some/catalog");
|
||||
|
||||
EXPECT_EQ(path.rebase("some/path/to/some/catalog", "some/path/to/some/catalog"),
|
||||
"some/path/to/some/catalog")
|
||||
<< "Rebasing to itself should not change the path.";
|
||||
|
||||
EXPECT_EQ(path.rebase("path/to", "new/base"), "")
|
||||
<< "Non-matching base path should return empty string to indicate 'NO'.";
|
||||
|
||||
/* Empty strings should be handled without crashing or other nasty side-effects. */
|
||||
AssetCatalogPath empty("");
|
||||
EXPECT_EQ(empty.rebase("path/to", "new/base"), "");
|
||||
EXPECT_EQ(empty.rebase("", "new/base"), "new/base");
|
||||
EXPECT_EQ(empty.rebase("", ""), "");
|
||||
}
|
||||
|
||||
TEST(AssetCatalogPathTest, parent)
|
||||
{
|
||||
const AssetCatalogPath ascii_path("path/with/missing/parents");
|
||||
EXPECT_EQ(ascii_path.parent(), "path/with/missing");
|
||||
|
||||
const AssetCatalogPath path("путь/в/Пермь/долог/и/далек");
|
||||
EXPECT_EQ(path.parent(), "путь/в/Пермь/долог/и");
|
||||
EXPECT_EQ(path.parent().parent(), "путь/в/Пермь/долог");
|
||||
EXPECT_EQ(path.parent().parent().parent(), "путь/в/Пермь");
|
||||
|
||||
const AssetCatalogPath one_level("one");
|
||||
EXPECT_EQ(one_level.parent(), "");
|
||||
|
||||
const AssetCatalogPath empty("");
|
||||
EXPECT_EQ(empty.parent(), "");
|
||||
}
|
||||
|
||||
} // namespace blender::bke::tests
|
@@ -57,6 +57,22 @@ class TestableAssetCatalogService : public AssetCatalogService {
|
||||
{
|
||||
return catalog_definition_file_.get();
|
||||
}
|
||||
|
||||
void create_missing_catalogs()
|
||||
{
|
||||
AssetCatalogService::create_missing_catalogs();
|
||||
}
|
||||
|
||||
int64_t count_catalogs_with_path(const CatalogFilePath &path)
|
||||
{
|
||||
int64_t count = 0;
|
||||
for (auto &catalog_uptr : catalogs_.values()) {
|
||||
if (catalog_uptr->path == path) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
};
|
||||
|
||||
class AssetCatalogTest : public testing::Test {
|
||||
@@ -106,7 +122,7 @@ class AssetCatalogTest : public testing::Test {
|
||||
EXPECT_EQ(expected_filename, actual_item.get_name());
|
||||
/* Does the computed number of parents match? */
|
||||
EXPECT_EQ(expected_path.parent_count, actual_item.count_parents());
|
||||
EXPECT_EQ(expected_path.name, actual_item.catalog_path());
|
||||
EXPECT_EQ(expected_path.name, actual_item.catalog_path().str());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -186,21 +202,21 @@ TEST_F(AssetCatalogTest, load_single_file)
|
||||
AssetCatalog *poses_ellie = service.find_catalog(UUID_POSES_ELLIE);
|
||||
ASSERT_NE(nullptr, poses_ellie);
|
||||
EXPECT_EQ(UUID_POSES_ELLIE, poses_ellie->catalog_id);
|
||||
EXPECT_EQ("character/Ellie/poselib", poses_ellie->path);
|
||||
EXPECT_EQ("character/Ellie/poselib", poses_ellie->path.str());
|
||||
EXPECT_EQ("POSES_ELLIE", poses_ellie->simple_name);
|
||||
|
||||
/* Test white-space stripping and support in the path. */
|
||||
AssetCatalog *poses_whitespace = service.find_catalog(UUID_POSES_ELLIE_WHITESPACE);
|
||||
ASSERT_NE(nullptr, poses_whitespace);
|
||||
EXPECT_EQ(UUID_POSES_ELLIE_WHITESPACE, poses_whitespace->catalog_id);
|
||||
EXPECT_EQ("character/Ellie/poselib/white space", poses_whitespace->path);
|
||||
EXPECT_EQ("character/Ellie/poselib/white space", poses_whitespace->path.str());
|
||||
EXPECT_EQ("POSES_ELLIE WHITESPACE", poses_whitespace->simple_name);
|
||||
|
||||
/* Test getting a UTF-8 catalog ID. */
|
||||
AssetCatalog *poses_ruzena = service.find_catalog(UUID_POSES_RUZENA);
|
||||
ASSERT_NE(nullptr, poses_ruzena);
|
||||
EXPECT_EQ(UUID_POSES_RUZENA, poses_ruzena->catalog_id);
|
||||
EXPECT_EQ("character/Ružena/poselib", poses_ruzena->path);
|
||||
EXPECT_EQ("character/Ružena/poselib", poses_ruzena->path.str());
|
||||
EXPECT_EQ("POSES_RUŽENA", poses_ruzena->simple_name);
|
||||
}
|
||||
|
||||
@@ -429,7 +445,7 @@ TEST_F(AssetCatalogTest, on_blendfile_save__with_existing_cdf)
|
||||
const AssetCatalog *cat = service.create_catalog("some/catalog/path");
|
||||
|
||||
const CatalogFilePath blendfilename = top_level_dir + "subdir/some_file.blend";
|
||||
ASSERT_TRUE(service.write_to_disk_on_blendfile_save(blendfilename.c_str()));
|
||||
ASSERT_TRUE(service.write_to_disk_on_blendfile_save(blendfilename));
|
||||
EXPECT_EQ(cdf_filename, service.get_catalog_definition_file()->file_path);
|
||||
|
||||
/* Test that the CDF was created in the expected location. */
|
||||
@@ -456,7 +472,7 @@ TEST_F(AssetCatalogTest, on_blendfile_save__from_memory_into_empty_directory)
|
||||
const AssetCatalog *cat = service.create_catalog("some/catalog/path");
|
||||
|
||||
const CatalogFilePath blendfilename = target_dir + "some_file.blend";
|
||||
ASSERT_TRUE(service.write_to_disk_on_blendfile_save(blendfilename.c_str()));
|
||||
ASSERT_TRUE(service.write_to_disk_on_blendfile_save(blendfilename));
|
||||
|
||||
/* Test that the CDF was created in the expected location. */
|
||||
const CatalogFilePath expected_cdf_path = target_dir +
|
||||
@@ -489,7 +505,7 @@ TEST_F(AssetCatalogTest, on_blendfile_save__from_memory_into_existing_cdf_and_me
|
||||
|
||||
/* Mock that the blend file is written to a subdirectory of the asset library. */
|
||||
const CatalogFilePath blendfilename = target_dir + "some_file.blend";
|
||||
ASSERT_TRUE(service.write_to_disk_on_blendfile_save(blendfilename.c_str()));
|
||||
ASSERT_TRUE(service.write_to_disk_on_blendfile_save(blendfilename));
|
||||
|
||||
/* Test that the CDF still exists in the expected location. */
|
||||
const CatalogFilePath backup_filename = writable_cdf_file + "~";
|
||||
@@ -534,7 +550,7 @@ TEST_F(AssetCatalogTest, on_blendfile_save__from_memory_into_existing_asset_lib)
|
||||
const AssetCatalog *cat = service.create_catalog("some/catalog/path");
|
||||
|
||||
/* Mock that the blend file is written to the directory already containing a CDF. */
|
||||
ASSERT_TRUE(service.write_to_disk_on_blendfile_save(blendfilename.c_str()));
|
||||
ASSERT_TRUE(service.write_to_disk_on_blendfile_save(blendfilename));
|
||||
|
||||
/* Test that the CDF still exists in the expected location. */
|
||||
EXPECT_TRUE(BLI_exists(writable_cdf_file.c_str()));
|
||||
@@ -588,7 +604,7 @@ TEST_F(AssetCatalogTest, create_first_catalog_from_scratch)
|
||||
AssetCatalog *written_cat = loaded_service.find_catalog(cat->catalog_id);
|
||||
ASSERT_NE(nullptr, written_cat);
|
||||
EXPECT_EQ(written_cat->catalog_id, cat->catalog_id);
|
||||
EXPECT_EQ(written_cat->path, cat->path);
|
||||
EXPECT_EQ(written_cat->path, cat->path.str());
|
||||
}
|
||||
|
||||
TEST_F(AssetCatalogTest, create_catalog_after_loading_file)
|
||||
@@ -640,7 +656,7 @@ TEST_F(AssetCatalogTest, create_catalog_path_cleanup)
|
||||
AssetCatalog *cat = service.create_catalog(" /some/path / ");
|
||||
|
||||
EXPECT_FALSE(BLI_uuid_is_nil(cat->catalog_id));
|
||||
EXPECT_EQ("some/path", cat->path);
|
||||
EXPECT_EQ("some/path", cat->path.str());
|
||||
EXPECT_EQ("some-path", cat->simple_name);
|
||||
}
|
||||
|
||||
@@ -652,7 +668,7 @@ TEST_F(AssetCatalogTest, create_catalog_simple_name)
|
||||
|
||||
EXPECT_FALSE(BLI_uuid_is_nil(cat->catalog_id));
|
||||
EXPECT_EQ("production/Spite Fright/Characters/Victora/Pose Library/Approved/Body Parts/Hands",
|
||||
cat->path);
|
||||
cat->path.str());
|
||||
EXPECT_EQ("...ht-Characters-Victora-Pose Library-Approved-Body Parts-Hands", cat->simple_name);
|
||||
}
|
||||
|
||||
@@ -718,7 +734,7 @@ TEST_F(AssetCatalogTest, update_catalog_path)
|
||||
AssetCatalogService::DEFAULT_CATALOG_FILENAME);
|
||||
|
||||
const AssetCatalog *orig_cat = service.find_catalog(UUID_POSES_RUZENA);
|
||||
const CatalogPath orig_path = orig_cat->path;
|
||||
const AssetCatalogPath orig_path = orig_cat->path;
|
||||
|
||||
service.update_catalog_path(UUID_POSES_RUZENA, "charlib/Ružena");
|
||||
|
||||
@@ -733,12 +749,12 @@ TEST_F(AssetCatalogTest, update_catalog_path)
|
||||
EXPECT_EQ(orig_cat->catalog_id, renamed_cat->catalog_id)
|
||||
<< "Changing the path should not change the catalog ID.";
|
||||
|
||||
EXPECT_EQ("charlib/Ružena", renamed_cat->path)
|
||||
EXPECT_EQ("charlib/Ružena", renamed_cat->path.str())
|
||||
<< "Changing the path should change the path. Surprise.";
|
||||
|
||||
EXPECT_EQ("charlib/Ružena/hand", service.find_catalog(UUID_POSES_RUZENA_HAND)->path)
|
||||
EXPECT_EQ("charlib/Ružena/hand", service.find_catalog(UUID_POSES_RUZENA_HAND)->path.str())
|
||||
<< "Changing the path should update children.";
|
||||
EXPECT_EQ("charlib/Ružena/face", service.find_catalog(UUID_POSES_RUZENA_FACE)->path)
|
||||
EXPECT_EQ("charlib/Ružena/face", service.find_catalog(UUID_POSES_RUZENA_FACE)->path.str())
|
||||
<< "Changing the path should update children.";
|
||||
}
|
||||
|
||||
@@ -775,7 +791,7 @@ TEST_F(AssetCatalogTest, merge_catalog_files)
|
||||
|
||||
/* When there are overlaps, the in-memory (i.e. last-saved) paths should win. */
|
||||
const AssetCatalog *ruzena_face = loaded_service.find_catalog(UUID_POSES_RUZENA_FACE);
|
||||
EXPECT_EQ("character/Ružena/poselib/face", ruzena_face->path);
|
||||
EXPECT_EQ("character/Ružena/poselib/face", ruzena_face->path.str());
|
||||
}
|
||||
|
||||
TEST_F(AssetCatalogTest, backups)
|
||||
@@ -846,21 +862,104 @@ TEST_F(AssetCatalogTest, order_by_path)
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(AssetCatalogTest, is_contained_in)
|
||||
TEST_F(AssetCatalogTest, create_missing_catalogs)
|
||||
{
|
||||
const AssetCatalog cat(BLI_uuid_generate_random(), "simple/path/child", "");
|
||||
TestableAssetCatalogService new_service;
|
||||
new_service.create_catalog("path/with/missing/parents");
|
||||
|
||||
EXPECT_FALSE(cat.is_contained_in("unrelated"));
|
||||
EXPECT_FALSE(cat.is_contained_in("sim"));
|
||||
EXPECT_FALSE(cat.is_contained_in("simple/pathx"));
|
||||
EXPECT_FALSE(cat.is_contained_in("simple/path/c"));
|
||||
EXPECT_FALSE(cat.is_contained_in("simple/path/child/grandchild"));
|
||||
EXPECT_FALSE(cat.is_contained_in("simple/path/"))
|
||||
<< "Non-normalized paths are not expected to work.";
|
||||
EXPECT_EQ(nullptr, new_service.find_catalog_by_path("path/with/missing"))
|
||||
<< "Missing parents should not be immediately created.";
|
||||
EXPECT_EQ(nullptr, new_service.find_catalog_by_path("")) << "Empty path should never be valid";
|
||||
|
||||
EXPECT_TRUE(cat.is_contained_in(""));
|
||||
EXPECT_TRUE(cat.is_contained_in("simple"));
|
||||
EXPECT_TRUE(cat.is_contained_in("simple/path"));
|
||||
new_service.create_missing_catalogs();
|
||||
|
||||
EXPECT_NE(nullptr, new_service.find_catalog_by_path("path/with/missing"));
|
||||
EXPECT_NE(nullptr, new_service.find_catalog_by_path("path/with"));
|
||||
EXPECT_NE(nullptr, new_service.find_catalog_by_path("path"));
|
||||
EXPECT_EQ(nullptr, new_service.find_catalog_by_path(""))
|
||||
<< "Empty path should never be valid, even when after missing catalogs";
|
||||
}
|
||||
|
||||
TEST_F(AssetCatalogTest, create_missing_catalogs_after_loading)
|
||||
{
|
||||
TestableAssetCatalogService loaded_service(asset_library_root_);
|
||||
loaded_service.load_from_disk();
|
||||
|
||||
const AssetCatalog *cat_char = loaded_service.find_catalog_by_path("character");
|
||||
const AssetCatalog *cat_ellie = loaded_service.find_catalog_by_path("character/Ellie");
|
||||
const AssetCatalog *cat_ruzena = loaded_service.find_catalog_by_path("character/Ružena");
|
||||
ASSERT_NE(nullptr, cat_char) << "Missing parents should be created immediately after loading.";
|
||||
ASSERT_NE(nullptr, cat_ellie) << "Missing parents should be created immediately after loading.";
|
||||
ASSERT_NE(nullptr, cat_ruzena) << "Missing parents should be created immediately after loading.";
|
||||
|
||||
AssetCatalogDefinitionFile *cdf = loaded_service.get_catalog_definition_file();
|
||||
ASSERT_NE(nullptr, cdf);
|
||||
EXPECT_TRUE(cdf->contains(cat_char->catalog_id)) << "Missing parents should be saved to a CDF.";
|
||||
EXPECT_TRUE(cdf->contains(cat_ellie->catalog_id)) << "Missing parents should be saved to a CDF.";
|
||||
EXPECT_TRUE(cdf->contains(cat_ruzena->catalog_id))
|
||||
<< "Missing parents should be saved to a CDF.";
|
||||
|
||||
/* Check that each missing parent is only created once. The CDF contains multiple paths that
|
||||
* could trigger the creation of missing parents, so this test makes sense. */
|
||||
EXPECT_EQ(1, loaded_service.count_catalogs_with_path("character"));
|
||||
EXPECT_EQ(1, loaded_service.count_catalogs_with_path("character/Ellie"));
|
||||
EXPECT_EQ(1, loaded_service.count_catalogs_with_path("character/Ružena"));
|
||||
}
|
||||
|
||||
TEST_F(AssetCatalogTest, create_catalog_filter)
|
||||
{
|
||||
AssetCatalogService service(asset_library_root_);
|
||||
service.load_from_disk();
|
||||
|
||||
/* Alias for the same catalog as the main one. */
|
||||
AssetCatalog *alias_ruzena = service.create_catalog("character/Ružena/poselib");
|
||||
/* Alias for a sub-catalog. */
|
||||
AssetCatalog *alias_ruzena_hand = service.create_catalog("character/Ružena/poselib/hand");
|
||||
|
||||
AssetCatalogFilter filter = service.create_catalog_filter(UUID_POSES_RUZENA);
|
||||
|
||||
/* Positive test for loaded-from-disk catalogs. */
|
||||
EXPECT_TRUE(filter.contains(UUID_POSES_RUZENA))
|
||||
<< "Main catalog should be included in the filter.";
|
||||
EXPECT_TRUE(filter.contains(UUID_POSES_RUZENA_HAND))
|
||||
<< "Sub-catalog should be included in the filter.";
|
||||
EXPECT_TRUE(filter.contains(UUID_POSES_RUZENA_FACE))
|
||||
<< "Sub-catalog should be included in the filter.";
|
||||
|
||||
/* Positive test for newly-created catalogs. */
|
||||
EXPECT_TRUE(filter.contains(alias_ruzena->catalog_id))
|
||||
<< "Alias of main catalog should be included in the filter.";
|
||||
EXPECT_TRUE(filter.contains(alias_ruzena_hand->catalog_id))
|
||||
<< "Alias of sub-catalog should be included in the filter.";
|
||||
|
||||
/* Negative test for unrelated catalogs. */
|
||||
EXPECT_FALSE(filter.contains(BLI_uuid_nil())) << "Nil catalog should not be included.";
|
||||
EXPECT_FALSE(filter.contains(UUID_ID_WITHOUT_PATH));
|
||||
EXPECT_FALSE(filter.contains(UUID_POSES_ELLIE));
|
||||
EXPECT_FALSE(filter.contains(UUID_POSES_ELLIE_WHITESPACE));
|
||||
EXPECT_FALSE(filter.contains(UUID_POSES_ELLIE_TRAILING_SLASH));
|
||||
EXPECT_FALSE(filter.contains(UUID_WITHOUT_SIMPLENAME));
|
||||
}
|
||||
|
||||
TEST_F(AssetCatalogTest, create_catalog_filter_for_unknown_uuid)
|
||||
{
|
||||
AssetCatalogService service;
|
||||
const bUUID unknown_uuid = BLI_uuid_generate_random();
|
||||
|
||||
AssetCatalogFilter filter = service.create_catalog_filter(unknown_uuid);
|
||||
EXPECT_TRUE(filter.contains(unknown_uuid));
|
||||
|
||||
EXPECT_FALSE(filter.contains(BLI_uuid_nil())) << "Nil catalog should not be included.";
|
||||
EXPECT_FALSE(filter.contains(UUID_POSES_ELLIE));
|
||||
}
|
||||
|
||||
TEST_F(AssetCatalogTest, create_catalog_filter_for_unassigned_assets)
|
||||
{
|
||||
AssetCatalogService service;
|
||||
|
||||
AssetCatalogFilter filter = service.create_catalog_filter(BLI_uuid_nil());
|
||||
EXPECT_TRUE(filter.contains(BLI_uuid_nil()));
|
||||
EXPECT_FALSE(filter.contains(UUID_POSES_ELLIE));
|
||||
}
|
||||
|
||||
} // namespace blender::bke::tests
|
||||
|
@@ -49,7 +49,7 @@ TEST(AssetLibraryTest, load_and_free_c_functions)
|
||||
const bUUID uuid_poses_ellie("df60e1f6-2259-475b-93d9-69a1b4a8db78");
|
||||
AssetCatalog *poses_ellie = service->find_catalog(uuid_poses_ellie);
|
||||
ASSERT_NE(nullptr, poses_ellie) << "unable to find POSES_ELLIE catalog";
|
||||
EXPECT_EQ("character/Ellie/poselib", poses_ellie->path);
|
||||
EXPECT_EQ("character/Ellie/poselib", poses_ellie->path.str());
|
||||
|
||||
BKE_asset_library_free(library_c_ptr);
|
||||
}
|
||||
|
@@ -597,7 +597,7 @@ static Collection *collection_duplicate_recursive(Main *bmain,
|
||||
}
|
||||
else if (collection_old->id.newid == NULL) {
|
||||
collection_new = (Collection *)BKE_id_copy_for_duplicate(
|
||||
bmain, (ID *)collection_old, duplicate_flags);
|
||||
bmain, (ID *)collection_old, duplicate_flags, LIB_ID_COPY_DEFAULT);
|
||||
|
||||
if (collection_new == collection_old) {
|
||||
return collection_new;
|
||||
|
@@ -1212,6 +1212,20 @@ void BKE_curvemapping_init(CurveMapping *cumap)
|
||||
}
|
||||
}
|
||||
|
||||
void BKE_curvemapping_table_F(const CurveMapping *cumap, float **array, int *size)
|
||||
{
|
||||
int a;
|
||||
|
||||
*size = CM_TABLE + 1;
|
||||
*array = MEM_callocN(sizeof(float) * (*size) * 4, "CurveMapping");
|
||||
|
||||
for (a = 0; a < *size; a++) {
|
||||
if (cumap->cm[0].table) {
|
||||
(*array)[a * 4 + 0] = cumap->cm[0].table[a].y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BKE_curvemapping_table_RGBA(const CurveMapping *cumap, float **array, int *size)
|
||||
{
|
||||
int a;
|
||||
|
@@ -1904,7 +1904,7 @@ KeyBlock *BKE_keyblock_add_ctime(Key *key, const char *name, const bool do_force
|
||||
return kb;
|
||||
}
|
||||
|
||||
/* only the active keyblock */
|
||||
/* Only the active key-block. */
|
||||
KeyBlock *BKE_keyblock_from_object(Object *ob)
|
||||
{
|
||||
Key *key = BKE_key_from_object(ob);
|
||||
@@ -2247,7 +2247,7 @@ void BKE_keyblock_convert_to_mesh(KeyBlock *kb, Mesh *me)
|
||||
* Computes normals (vertices, polygons and/or loops ones) of given mesh for given shape key.
|
||||
*
|
||||
* \param kb: the KeyBlock to use to compute normals.
|
||||
* \param mesh: the Mesh to apply keyblock to.
|
||||
* \param mesh: the Mesh to apply key-block to.
|
||||
* \param r_vertnors: if non-NULL, an array of vectors, same length as number of vertices.
|
||||
* \param r_polynors: if non-NULL, an array of vectors, same length as number of polygons.
|
||||
* \param r_loopnors: if non-NULL, an array of vectors, same length as number of loops.
|
||||
@@ -2345,7 +2345,7 @@ void BKE_keyblock_update_from_vertcos(Object *ob, KeyBlock *kb, const float (*ve
|
||||
return;
|
||||
}
|
||||
|
||||
/* Copy coords to keyblock */
|
||||
/* Copy coords to key-block. */
|
||||
if (ELEM(ob->type, OB_MESH, OB_LATTICE)) {
|
||||
for (a = 0; a < tot; a++, fp += 3, co++) {
|
||||
copy_v3_v3(fp, *co);
|
||||
@@ -2405,7 +2405,7 @@ void BKE_keyblock_convert_from_vertcos(Object *ob, KeyBlock *kb, const float (*v
|
||||
|
||||
kb->data = MEM_mallocN(tot * elemsize, __func__);
|
||||
|
||||
/* Copy coords to keyblock */
|
||||
/* Copy coords to key-block. */
|
||||
BKE_keyblock_update_from_vertcos(ob, kb, vertCos);
|
||||
}
|
||||
|
||||
@@ -2594,7 +2594,7 @@ bool BKE_keyblock_move(Object *ob, int org_index, int new_index)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if given keyblock (as index) is used as basis by others in given key.
|
||||
* Check if given key-block (as index) is used as basis by others in given key.
|
||||
*/
|
||||
bool BKE_keyblock_is_basis(Key *key, const int index)
|
||||
{
|
||||
|
@@ -674,7 +674,10 @@ ID *BKE_id_copy(Main *bmain, const ID *id)
|
||||
* Invokes the appropriate copy method for the block and returns the result in
|
||||
* newid, unless test. Returns true if the block can be copied.
|
||||
*/
|
||||
ID *BKE_id_copy_for_duplicate(Main *bmain, ID *id, const eDupli_ID_Flags duplicate_flags)
|
||||
ID *BKE_id_copy_for_duplicate(Main *bmain,
|
||||
ID *id,
|
||||
const eDupli_ID_Flags duplicate_flags,
|
||||
const int copy_flags)
|
||||
{
|
||||
if (id == NULL) {
|
||||
return id;
|
||||
@@ -685,7 +688,7 @@ ID *BKE_id_copy_for_duplicate(Main *bmain, ID *id, const eDupli_ID_Flags duplica
|
||||
return id;
|
||||
}
|
||||
|
||||
ID *id_new = BKE_id_copy(bmain, id);
|
||||
ID *id_new = BKE_id_copy_ex(bmain, id, NULL, copy_flags);
|
||||
/* Copying add one user by default, need to get rid of that one. */
|
||||
id_us_min(id_new);
|
||||
ID_NEW_SET(id, id_new);
|
||||
|
@@ -538,7 +538,7 @@ void ntreeBlendWrite(BlendWriter *writer, bNodeTree *ntree)
|
||||
if (node->storage) {
|
||||
/* could be handlerized at some point, now only 1 exception still */
|
||||
if (ELEM(ntree->type, NTREE_SHADER, NTREE_GEOMETRY) &&
|
||||
ELEM(node->type, SH_NODE_CURVE_VEC, SH_NODE_CURVE_RGB)) {
|
||||
ELEM(node->type, SH_NODE_CURVE_VEC, SH_NODE_CURVE_RGB, SH_NODE_CURVE_FLOAT)) {
|
||||
BKE_curvemapping_blend_write(writer, (const CurveMapping *)node->storage);
|
||||
}
|
||||
else if ((ntree->type == NTREE_GEOMETRY) &&
|
||||
@@ -714,6 +714,7 @@ void ntreeBlendReadData(BlendDataReader *reader, bNodeTree *ntree)
|
||||
switch (node->type) {
|
||||
case SH_NODE_CURVE_VEC:
|
||||
case SH_NODE_CURVE_RGB:
|
||||
case SH_NODE_CURVE_FLOAT:
|
||||
case CMP_NODE_TIME:
|
||||
case CMP_NODE_CURVE_VEC:
|
||||
case CMP_NODE_CURVE_RGB:
|
||||
@@ -5574,6 +5575,7 @@ static void registerShaderNodes()
|
||||
register_node_type_sh_shadertorgb();
|
||||
register_node_type_sh_normal();
|
||||
register_node_type_sh_mapping();
|
||||
register_node_type_sh_curve_float();
|
||||
register_node_type_sh_curve_vec();
|
||||
register_node_type_sh_curve_rgb();
|
||||
register_node_type_sh_map_range();
|
||||
@@ -5707,11 +5709,14 @@ static void registerGeometryNodes()
|
||||
{
|
||||
register_node_type_geo_group();
|
||||
|
||||
register_node_type_geo_legacy_curve_set_handles();
|
||||
register_node_type_geo_legacy_attribute_proximity();
|
||||
register_node_type_geo_legacy_attribute_randomize();
|
||||
register_node_type_geo_legacy_material_assign();
|
||||
register_node_type_geo_legacy_select_by_material();
|
||||
register_node_type_geo_legacy_curve_spline_type();
|
||||
register_node_type_geo_legacy_curve_reverse();
|
||||
register_node_type_geo_legacy_curve_subdivide();
|
||||
|
||||
register_node_type_geo_align_rotation_to_vector();
|
||||
register_node_type_geo_attribute_capture();
|
||||
@@ -5764,6 +5769,7 @@ static void registerGeometryNodes()
|
||||
register_node_type_geo_input_normal();
|
||||
register_node_type_geo_input_position();
|
||||
register_node_type_geo_input_tangent();
|
||||
register_node_type_geo_input_spline_length();
|
||||
register_node_type_geo_instance_on_points();
|
||||
register_node_type_geo_is_viewport();
|
||||
register_node_type_geo_join_geometry();
|
||||
@@ -5818,6 +5824,7 @@ static void registerFunctionNodes()
|
||||
register_node_type_fn_input_string();
|
||||
register_node_type_fn_input_vector();
|
||||
register_node_type_fn_random_value();
|
||||
register_node_type_fn_rotate_euler();
|
||||
register_node_type_fn_string_length();
|
||||
register_node_type_fn_string_substring();
|
||||
register_node_type_fn_value_to_string();
|
||||
|
@@ -2634,10 +2634,16 @@ Object *BKE_object_duplicate(Main *bmain,
|
||||
{
|
||||
const bool is_subprocess = (duplicate_options & LIB_ID_DUPLICATE_IS_SUBPROCESS) != 0;
|
||||
const bool is_root_id = (duplicate_options & LIB_ID_DUPLICATE_IS_ROOT_ID) != 0;
|
||||
int copy_flags = LIB_ID_COPY_DEFAULT;
|
||||
|
||||
if (!is_subprocess) {
|
||||
BKE_main_id_newptr_and_tag_clear(bmain);
|
||||
}
|
||||
else {
|
||||
/* In case copying object is a sub-process of collection (or scene) copying, do not try to
|
||||
* re-assign RB objects to existing RBW collections. */
|
||||
copy_flags |= LIB_ID_COPY_RIGID_BODY_NO_COLLECTION_HANDLING;
|
||||
}
|
||||
if (is_root_id) {
|
||||
/* In case root duplicated ID is linked, assume we want to get a local copy of it and duplicate
|
||||
* all expected linked data. */
|
||||
@@ -2649,7 +2655,7 @@ Object *BKE_object_duplicate(Main *bmain,
|
||||
|
||||
Material ***matarar;
|
||||
|
||||
Object *obn = (Object *)BKE_id_copy_for_duplicate(bmain, &ob->id, dupflag);
|
||||
Object *obn = (Object *)BKE_id_copy_for_duplicate(bmain, &ob->id, dupflag, copy_flags);
|
||||
|
||||
/* 0 == full linked. */
|
||||
if (dupflag == 0) {
|
||||
@@ -2658,13 +2664,13 @@ Object *BKE_object_duplicate(Main *bmain,
|
||||
|
||||
if (dupflag & USER_DUP_MAT) {
|
||||
for (int i = 0; i < obn->totcol; i++) {
|
||||
BKE_id_copy_for_duplicate(bmain, (ID *)obn->mat[i], dupflag);
|
||||
BKE_id_copy_for_duplicate(bmain, (ID *)obn->mat[i], dupflag, copy_flags);
|
||||
}
|
||||
}
|
||||
if (dupflag & USER_DUP_PSYS) {
|
||||
ParticleSystem *psys;
|
||||
for (psys = obn->particlesystem.first; psys; psys = psys->next) {
|
||||
BKE_id_copy_for_duplicate(bmain, (ID *)psys->part, dupflag);
|
||||
BKE_id_copy_for_duplicate(bmain, (ID *)psys->part, dupflag, copy_flags);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2675,77 +2681,77 @@ Object *BKE_object_duplicate(Main *bmain,
|
||||
switch (obn->type) {
|
||||
case OB_MESH:
|
||||
if (dupflag & USER_DUP_MESH) {
|
||||
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag);
|
||||
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags);
|
||||
}
|
||||
break;
|
||||
case OB_CURVE:
|
||||
if (dupflag & USER_DUP_CURVE) {
|
||||
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag);
|
||||
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags);
|
||||
}
|
||||
break;
|
||||
case OB_SURF:
|
||||
if (dupflag & USER_DUP_SURF) {
|
||||
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag);
|
||||
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags);
|
||||
}
|
||||
break;
|
||||
case OB_FONT:
|
||||
if (dupflag & USER_DUP_FONT) {
|
||||
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag);
|
||||
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags);
|
||||
}
|
||||
break;
|
||||
case OB_MBALL:
|
||||
if (dupflag & USER_DUP_MBALL) {
|
||||
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag);
|
||||
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags);
|
||||
}
|
||||
break;
|
||||
case OB_LAMP:
|
||||
if (dupflag & USER_DUP_LAMP) {
|
||||
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag);
|
||||
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags);
|
||||
}
|
||||
break;
|
||||
case OB_ARMATURE:
|
||||
if (dupflag & USER_DUP_ARM) {
|
||||
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag);
|
||||
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags);
|
||||
}
|
||||
break;
|
||||
case OB_LATTICE:
|
||||
if (dupflag != 0) {
|
||||
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag);
|
||||
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags);
|
||||
}
|
||||
break;
|
||||
case OB_CAMERA:
|
||||
if (dupflag != 0) {
|
||||
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag);
|
||||
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags);
|
||||
}
|
||||
break;
|
||||
case OB_LIGHTPROBE:
|
||||
if (dupflag & USER_DUP_LIGHTPROBE) {
|
||||
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag);
|
||||
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags);
|
||||
}
|
||||
break;
|
||||
case OB_SPEAKER:
|
||||
if (dupflag != 0) {
|
||||
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag);
|
||||
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags);
|
||||
}
|
||||
break;
|
||||
case OB_GPENCIL:
|
||||
if (dupflag & USER_DUP_GPENCIL) {
|
||||
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag);
|
||||
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags);
|
||||
}
|
||||
break;
|
||||
case OB_HAIR:
|
||||
if (dupflag & USER_DUP_HAIR) {
|
||||
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag);
|
||||
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags);
|
||||
}
|
||||
break;
|
||||
case OB_POINTCLOUD:
|
||||
if (dupflag & USER_DUP_POINTCLOUD) {
|
||||
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag);
|
||||
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags);
|
||||
}
|
||||
break;
|
||||
case OB_VOLUME:
|
||||
if (dupflag & USER_DUP_VOLUME) {
|
||||
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag);
|
||||
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -2756,7 +2762,7 @@ Object *BKE_object_duplicate(Main *bmain,
|
||||
matarar = BKE_object_material_array_p(obn);
|
||||
if (matarar) {
|
||||
for (int i = 0; i < obn->totcol; i++) {
|
||||
BKE_id_copy_for_duplicate(bmain, (ID *)(*matarar)[i], dupflag);
|
||||
BKE_id_copy_for_duplicate(bmain, (ID *)(*matarar)[i], dupflag, copy_flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -4619,11 +4619,11 @@ void psys_get_particle_on_path(ParticleSimulationData *sim,
|
||||
pind.cache = cached ? psys->pointcache : NULL;
|
||||
pind.epoint = NULL;
|
||||
pind.bspline = (psys->part->flag & PART_HAIR_BSPLINE);
|
||||
/* pind.dm disabled in editmode means we don't get effectors taken into
|
||||
* account when subdividing for instance */
|
||||
/* `pind.dm` disabled in edit-mode means we don't get effectors taken into
|
||||
* account when subdividing for instance. */
|
||||
pind.mesh = psys_in_edit_mode(sim->depsgraph, psys) ?
|
||||
NULL :
|
||||
psys->hair_out_mesh; /* XXX Sybren EEK */
|
||||
psys->hair_out_mesh; /* XXX(@sybren) EEK. */
|
||||
init_particle_interpolation(sim->ob, psys, pa, &pind);
|
||||
do_particle_interpolation(psys, p, pa, t, &pind, state);
|
||||
|
||||
|
@@ -302,7 +302,7 @@ void BKE_rigidbody_object_copy(Main *bmain, Object *ob_dst, const Object *ob_src
|
||||
ob_dst->rigidbody_object = rigidbody_copy_object(ob_src, flag);
|
||||
ob_dst->rigidbody_constraint = rigidbody_copy_constraint(ob_src, flag);
|
||||
|
||||
if (flag & LIB_ID_CREATE_NO_MAIN) {
|
||||
if ((flag & (LIB_ID_CREATE_NO_MAIN | LIB_ID_COPY_RIGID_BODY_NO_COLLECTION_HANDLING)) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1211,8 +1211,8 @@ RigidBodyWorld *BKE_rigidbody_world_copy(RigidBodyWorld *rbw, const int flag)
|
||||
id_us_plus((ID *)rbw_copy->constraints);
|
||||
}
|
||||
|
||||
if ((flag & LIB_ID_CREATE_NO_MAIN) == 0) {
|
||||
/* This is a regular copy, and not a CoW copy for depsgraph evaluation */
|
||||
if ((flag & LIB_ID_COPY_SET_COPIED_ON_WRITE) == 0) {
|
||||
/* This is a regular copy, and not a CoW copy for depsgraph evaluation. */
|
||||
rbw_copy->shared = MEM_callocN(sizeof(*rbw_copy->shared), "RigidBodyWorld_Shared");
|
||||
BKE_ptcache_copy_list(&rbw_copy->shared->ptcaches, &rbw->shared->ptcaches, LIB_ID_COPY_CACHES);
|
||||
rbw_copy->shared->pointcache = rbw_copy->shared->ptcaches.first;
|
||||
|
@@ -1801,6 +1801,7 @@ Scene *BKE_scene_duplicate(Main *bmain, Scene *sce, eSceneCopyMethod type)
|
||||
/* Scene duplication is always root of duplication currently. */
|
||||
const bool is_subprocess = false;
|
||||
const bool is_root_id = true;
|
||||
const int copy_flags = LIB_ID_COPY_DEFAULT;
|
||||
|
||||
if (!is_subprocess) {
|
||||
BKE_main_id_newptr_and_tag_clear(bmain);
|
||||
@@ -1816,21 +1817,40 @@ Scene *BKE_scene_duplicate(Main *bmain, Scene *sce, eSceneCopyMethod type)
|
||||
/* Copy Freestyle LineStyle datablocks. */
|
||||
LISTBASE_FOREACH (ViewLayer *, view_layer_dst, &sce_copy->view_layers) {
|
||||
LISTBASE_FOREACH (FreestyleLineSet *, lineset, &view_layer_dst->freestyle_config.linesets) {
|
||||
BKE_id_copy_for_duplicate(bmain, (ID *)lineset->linestyle, duplicate_flags);
|
||||
BKE_id_copy_for_duplicate(bmain, (ID *)lineset->linestyle, duplicate_flags, copy_flags);
|
||||
}
|
||||
}
|
||||
|
||||
/* Full copy of world (included animations) */
|
||||
BKE_id_copy_for_duplicate(bmain, (ID *)sce->world, duplicate_flags);
|
||||
BKE_id_copy_for_duplicate(bmain, (ID *)sce->world, duplicate_flags, copy_flags);
|
||||
|
||||
/* Full copy of GreasePencil. */
|
||||
BKE_id_copy_for_duplicate(bmain, (ID *)sce->gpd, duplicate_flags);
|
||||
BKE_id_copy_for_duplicate(bmain, (ID *)sce->gpd, duplicate_flags, copy_flags);
|
||||
|
||||
/* Deep-duplicate collections and objects (using preferences' settings for which sub-data to
|
||||
* duplicate along the object itself). */
|
||||
BKE_collection_duplicate(
|
||||
bmain, NULL, sce_copy->master_collection, duplicate_flags, LIB_ID_DUPLICATE_IS_SUBPROCESS);
|
||||
|
||||
/* Rigid body world collections may not be instantiated as scene's collections, ensure they
|
||||
* also get properly duplicated. */
|
||||
if (sce_copy->rigidbody_world != NULL) {
|
||||
if (sce_copy->rigidbody_world->group != NULL) {
|
||||
BKE_collection_duplicate(bmain,
|
||||
NULL,
|
||||
sce_copy->rigidbody_world->group,
|
||||
duplicate_flags,
|
||||
LIB_ID_DUPLICATE_IS_SUBPROCESS);
|
||||
}
|
||||
if (sce_copy->rigidbody_world->constraints != NULL) {
|
||||
BKE_collection_duplicate(bmain,
|
||||
NULL,
|
||||
sce_copy->rigidbody_world->constraints,
|
||||
duplicate_flags,
|
||||
LIB_ID_DUPLICATE_IS_SUBPROCESS);
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_subprocess) {
|
||||
/* This code will follow into all ID links using an ID tagged with LIB_TAG_NEW. */
|
||||
BKE_libblock_relink_to_newid(&sce_copy->id);
|
||||
|
@@ -2295,7 +2295,7 @@ static void softbody_calc_forces(
|
||||
sb_sfesf_threads_run(depsgraph, scene, ob, timenow, sb->totspring, NULL);
|
||||
}
|
||||
|
||||
/* after spring scan because it uses Effoctors too */
|
||||
/* After spring scan because it uses effectors too. */
|
||||
ListBase *effectors = BKE_effectors_create(depsgraph, ob, NULL, sb->effector_weights, false);
|
||||
|
||||
if (do_deflector) {
|
||||
|
@@ -3014,6 +3014,61 @@ static int channels_average_error_sort(const void *a, const void *b)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int compare_firstlast_putting_undefined_first(
|
||||
bool inverse, bool a_markerless, int a_value, bool b_markerless, int b_value)
|
||||
{
|
||||
if (a_markerless && b_markerless) {
|
||||
/* Neither channel has not-disabled markers, return whatever. */
|
||||
return 0;
|
||||
}
|
||||
if (a_markerless) {
|
||||
/* Put the markerless channel first. */
|
||||
return 0;
|
||||
}
|
||||
if (b_markerless) {
|
||||
/* Put the markerless channel first. */
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Both channels have markers. */
|
||||
|
||||
if (inverse) {
|
||||
if (a_value < b_value) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (a_value > b_value) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int channels_start_sort(const void *a, const void *b)
|
||||
{
|
||||
const MovieTrackingDopesheetChannel *channel_a = a;
|
||||
const MovieTrackingDopesheetChannel *channel_b = b;
|
||||
|
||||
return compare_firstlast_putting_undefined_first(false,
|
||||
channel_a->tot_segment == 0,
|
||||
channel_a->first_not_disabled_marker_framenr,
|
||||
channel_b->tot_segment == 0,
|
||||
channel_b->first_not_disabled_marker_framenr);
|
||||
}
|
||||
|
||||
static int channels_end_sort(const void *a, const void *b)
|
||||
{
|
||||
const MovieTrackingDopesheetChannel *channel_a = a;
|
||||
const MovieTrackingDopesheetChannel *channel_b = b;
|
||||
|
||||
return compare_firstlast_putting_undefined_first(false,
|
||||
channel_a->tot_segment == 0,
|
||||
channel_a->last_not_disabled_marker_framenr,
|
||||
channel_b->tot_segment == 0,
|
||||
channel_b->last_not_disabled_marker_framenr);
|
||||
}
|
||||
|
||||
static int channels_alpha_inverse_sort(const void *a, const void *b)
|
||||
{
|
||||
if (channels_alpha_sort(a, b)) {
|
||||
@@ -3053,22 +3108,51 @@ static int channels_average_error_inverse_sort(const void *a, const void *b)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int channels_start_inverse_sort(const void *a, const void *b)
|
||||
{
|
||||
const MovieTrackingDopesheetChannel *channel_a = a;
|
||||
const MovieTrackingDopesheetChannel *channel_b = b;
|
||||
|
||||
return compare_firstlast_putting_undefined_first(true,
|
||||
channel_a->tot_segment == 0,
|
||||
channel_a->first_not_disabled_marker_framenr,
|
||||
channel_b->tot_segment == 0,
|
||||
channel_b->first_not_disabled_marker_framenr);
|
||||
}
|
||||
|
||||
static int channels_end_inverse_sort(const void *a, const void *b)
|
||||
{
|
||||
const MovieTrackingDopesheetChannel *channel_a = a;
|
||||
const MovieTrackingDopesheetChannel *channel_b = b;
|
||||
|
||||
return compare_firstlast_putting_undefined_first(true,
|
||||
channel_a->tot_segment == 0,
|
||||
channel_a->last_not_disabled_marker_framenr,
|
||||
channel_b->tot_segment == 0,
|
||||
channel_b->last_not_disabled_marker_framenr);
|
||||
}
|
||||
|
||||
/* Calculate frames segments at which track is tracked continuously. */
|
||||
static void tracking_dopesheet_channels_segments_calc(MovieTrackingDopesheetChannel *channel)
|
||||
{
|
||||
MovieTrackingTrack *track = channel->track;
|
||||
int i, segment;
|
||||
bool first_not_disabled_marker_framenr_set;
|
||||
|
||||
channel->tot_segment = 0;
|
||||
channel->max_segment = 0;
|
||||
channel->total_frames = 0;
|
||||
|
||||
channel->first_not_disabled_marker_framenr = 0;
|
||||
channel->last_not_disabled_marker_framenr = 0;
|
||||
|
||||
/* TODO(sergey): looks a bit code-duplicated, need to look into
|
||||
* logic de-duplication here.
|
||||
*/
|
||||
|
||||
/* count */
|
||||
i = 0;
|
||||
first_not_disabled_marker_framenr_set = false;
|
||||
while (i < track->markersnr) {
|
||||
MovieTrackingMarker *marker = &track->markers[i];
|
||||
|
||||
@@ -3086,6 +3170,12 @@ static void tracking_dopesheet_channels_segments_calc(MovieTrackingDopesheetChan
|
||||
break;
|
||||
}
|
||||
|
||||
if (!first_not_disabled_marker_framenr_set) {
|
||||
channel->first_not_disabled_marker_framenr = marker->framenr;
|
||||
first_not_disabled_marker_framenr_set = true;
|
||||
}
|
||||
channel->last_not_disabled_marker_framenr = marker->framenr;
|
||||
|
||||
prev_fra = marker->framenr;
|
||||
len++;
|
||||
i++;
|
||||
@@ -3203,6 +3293,12 @@ static void tracking_dopesheet_channels_sort(MovieTracking *tracking,
|
||||
else if (sort_method == TRACKING_DOPE_SORT_AVERAGE_ERROR) {
|
||||
BLI_listbase_sort(&dopesheet->channels, channels_average_error_inverse_sort);
|
||||
}
|
||||
else if (sort_method == TRACKING_DOPE_SORT_START) {
|
||||
BLI_listbase_sort(&dopesheet->channels, channels_start_inverse_sort);
|
||||
}
|
||||
else if (sort_method == TRACKING_DOPE_SORT_END) {
|
||||
BLI_listbase_sort(&dopesheet->channels, channels_end_inverse_sort);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (sort_method == TRACKING_DOPE_SORT_NAME) {
|
||||
@@ -3217,6 +3313,12 @@ static void tracking_dopesheet_channels_sort(MovieTracking *tracking,
|
||||
else if (sort_method == TRACKING_DOPE_SORT_AVERAGE_ERROR) {
|
||||
BLI_listbase_sort(&dopesheet->channels, channels_average_error_sort);
|
||||
}
|
||||
else if (sort_method == TRACKING_DOPE_SORT_START) {
|
||||
BLI_listbase_sort(&dopesheet->channels, channels_start_sort);
|
||||
}
|
||||
else if (sort_method == TRACKING_DOPE_SORT_END) {
|
||||
BLI_listbase_sort(&dopesheet->channels, channels_end_sort);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user