Compare commits
268 Commits
temp-node-
...
temp-ui-tw
Author | SHA1 | Date | |
---|---|---|---|
f0731bd5ac | |||
4a3464050c | |||
b536605e78 | |||
4882208633 | |||
cc636db8f2 | |||
f806bd8261 | |||
fc4886a314 | |||
326bd76d3b | |||
a80a2f07b7 | |||
76238af213 | |||
8ca7250982 | |||
2c5661682b | |||
4e50c5f4fb | |||
dc4c2815a6 | |||
![]() |
e62ce9e08e | ||
23d9953c80 | |||
ce6a24976a | |||
f2b86471ea | |||
37003cbbc1 | |||
87a3cb3bff | |||
8c55333a8e | |||
fc6228bd85 | |||
93e92ac126 | |||
357acd1d50 | |||
e43fcc014a | |||
3c3669894f | |||
6e48a51af7 | |||
606271966e | |||
e0e7a5522f | |||
e7274dedc4 | |||
cc8fa3ee90 | |||
dfdc9c6219 | |||
b6195f6664 | |||
272a38e0c2 | |||
c9af025936 | |||
1e5cfebf66 | |||
57272d598d | |||
ee79bde54d | |||
c4dca65228 | |||
3b1a243039 | |||
2305f270c5 | |||
adc084a3e9 | |||
ae86584404 | |||
98fe05fb5b | |||
1833ebea31 | |||
bf354cde96 | |||
64d07ffcc3 | |||
17e2d54a3d | |||
a812fe8ceb | |||
2f52f5683c | |||
06c3bac23b | |||
fb34cdc7da | |||
0998856c92 | |||
8fc97a871f | |||
5d5a753d96 | |||
e1e75bd62c | |||
a8d6a86981 | |||
c206fa9627 | |||
f2da98d816 | |||
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 | |||
20c27a5151 | |||
150b879969 | |||
76c49d5884 | |||
71072a904f | |||
cb0ab7bccc | |||
7ae6f9dede | |||
52fb64024b | |||
c2ab184162 | |||
3107db97e2 | |||
3ea201bfa6 | |||
f1f3552b9b | |||
03a10c31d6 | |||
3486a23b1e | |||
03fa0ef033 | |||
9f576b7db8 | |||
451d734f80 | |||
3dc53b1a29 | |||
db70f583a2 | |||
5d4ca28f8c | |||
de5b91205a | |||
bd67caa872 | |||
a7e75b855e | |||
f60420116c | |||
0d206598f7 | |||
b44b39d581 | |||
638f972453 | |||
24902401ba | |||
fc406830f7 | |||
c9259808a1 | |||
c3045118b3 | |||
68e229feea | |||
ea6838ec6d | |||
d612181827 | |||
7b1a1bc8eb | |||
6092fa3702 | |||
a17e895d7d | |||
5e5a98e51a | |||
![]() |
8129dd3189 | ||
e831f41d0b | |||
fe2efb6866 | |||
2f06f35513 | |||
f91718bdd9 | |||
c0fb68a93d | |||
13a0a0d592 | |||
ff3345a554 | |||
9ec46174a0 | |||
500d0e1fda | |||
711673caff | |||
2e309eb86a | |||
f4ee2de310 | |||
a241b69982 | |||
b4a6d70789 | |||
89fe4220bc | |||
2ea4b17c95 | |||
f25c1f29b0 | |||
084719ff1b | |||
23feb6770a | |||
7f8ba9dbb4 | |||
fa165fc19e | |||
1cf11da339 | |||
301c0c247c | |||
66e0182b80 | |||
4a538f0add | |||
15acdf04d9 | |||
b4100bfa59 | |||
d82a73c67a | |||
05b2ed822d | |||
71e29bf0b0 | |||
83449ab7d0 | |||
9cee00621e | |||
7df30bb5f6 | |||
abd31665dd | |||
68b306156c | |||
05cdd5f177 | |||
a0949f0c30 | |||
a81e121d8d | |||
5d3d2d839d | |||
5be80aea08 | |||
081dc2cc92 | |||
2923409795 | |||
14a9a3687b | |||
0e1270ea9f | |||
5839e55295 | |||
3f0d9a89d3 | |||
![]() |
0706f7a08a | ||
a969417f37 | |||
52bbd01895 | |||
7e87a6963c | |||
ba5859a5d7 | |||
![]() |
149501c774 | ||
8ec6b1fc4c | |||
f3241b8e34 | |||
83bdfba499 | |||
e31d6560a2 | |||
aeeb7c82c6 | |||
bb310ff14e | |||
9d63273bf8 | |||
1a677512b7 | |||
6312f82324 | |||
1f8eab534d | |||
dea0dbc5ef | |||
0b8524c578 | |||
8c61473869 | |||
4f65ad890b | |||
584b8a0def | |||
47a7f02339 | |||
c0aa938931 | |||
2aca22d60f | |||
926e839f80 | |||
4ddce5f1c4 | |||
![]() |
7a373ecbf5 | ||
5a8766180d | |||
cef3d4225c | |||
abe1665569 | |||
86761e4950 | |||
494469897c | |||
205ccb8bbe | |||
cbd43cedc3 | |||
975d21a1a2 | |||
14af051591 | |||
52ee98859d | |||
c1e21847b9 | |||
296814ab92 | |||
0da8a60a37 | |||
667e24d8a0 | |||
84d5be6993 | |||
![]() |
42aaff79db | ||
0647f4f511 | |||
a4b39da6c7 | |||
ae42e4be1d | |||
8085f1e320 | |||
cb23adf94b | |||
ba7f03c1bb | |||
e262b00918 | |||
48c2c7b1b4 | |||
83b342fd91 | |||
18a3b9b238 | |||
870404455b | |||
323997ace3 | |||
72003be8fb | |||
82323cafb9 | |||
7e4af18960 | |||
f3b2cbcb81 | |||
811dc12e34 | |||
1cba89572f | |||
ad12ff5376 | |||
49777cb729 | |||
a64d281528 | |||
b9fa39c8e9 | |||
a62ca11d7f |
@@ -24,6 +24,7 @@ import project_source_info
|
||||
import subprocess
|
||||
import sys
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
from typing import (
|
||||
Any,
|
||||
@@ -35,7 +36,6 @@ USE_QUIET = (os.environ.get("QUIET", None) is not None)
|
||||
|
||||
CHECKER_IGNORE_PREFIX = [
|
||||
"extern",
|
||||
"intern/moto",
|
||||
]
|
||||
|
||||
CHECKER_BIN = "cppcheck"
|
||||
@@ -47,13 +47,19 @@ CHECKER_ARGS = [
|
||||
"--max-configs=1", # speeds up execution
|
||||
# "--check-config", # when includes are missing
|
||||
"--enable=all", # if you want sixty hundred pedantic suggestions
|
||||
|
||||
# Quiet output, otherwise all defines/includes are printed (overly verbose).
|
||||
# Only enable this for troubleshooting (if defines are not set as expected for example).
|
||||
"--quiet",
|
||||
|
||||
# NOTE: `--cppcheck-build-dir=<dir>` is added later as a temporary directory.
|
||||
]
|
||||
|
||||
if USE_QUIET:
|
||||
CHECKER_ARGS.append("--quiet")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
def cppcheck() -> None:
|
||||
source_info = project_source_info.build_info(ignore_prefix_list=CHECKER_IGNORE_PREFIX)
|
||||
source_defines = project_source_info.build_defines_as_args()
|
||||
|
||||
@@ -78,7 +84,10 @@ def main() -> None:
|
||||
percent_str = "[" + ("%.2f]" % percent).rjust(7) + " %:"
|
||||
|
||||
sys.stdout.flush()
|
||||
sys.stdout.write("%s " % percent_str)
|
||||
sys.stdout.write("%s %s\n" % (
|
||||
percent_str,
|
||||
os.path.relpath(c, project_source_info.SOURCE_DIR)
|
||||
))
|
||||
|
||||
return subprocess.Popen(cmd)
|
||||
|
||||
@@ -90,5 +99,11 @@ def main() -> None:
|
||||
print("Finished!")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
CHECKER_ARGS.append("--cppcheck-build-dir=" + temp_dir)
|
||||
cppcheck()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
@@ -243,7 +243,9 @@ def build_defines_as_args() -> List[str]:
|
||||
# use this module.
|
||||
def queue_processes(
|
||||
process_funcs: Sequence[Tuple[Callable[..., subprocess.Popen[Any]], Tuple[Any, ...]]],
|
||||
*,
|
||||
job_total: int =-1,
|
||||
sleep: float = 0.1,
|
||||
) -> None:
|
||||
""" Takes a list of function arg pairs, each function must return a process
|
||||
"""
|
||||
@@ -271,14 +273,20 @@ def queue_processes(
|
||||
|
||||
if len(processes) <= job_total:
|
||||
break
|
||||
else:
|
||||
time.sleep(0.1)
|
||||
time.sleep(sleep)
|
||||
|
||||
sys.stdout.flush()
|
||||
sys.stderr.flush()
|
||||
|
||||
processes.append(func(*args))
|
||||
|
||||
# Don't return until all jobs have finished.
|
||||
while 1:
|
||||
processes[:] = [p for p in processes if p.poll() is None]
|
||||
if not processes:
|
||||
break
|
||||
time.sleep(sleep)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
if not os.path.exists(join(CMAKE_DIR, "CMakeCache.txt")):
|
||||
|
@@ -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)
|
@@ -1101,6 +1101,7 @@ context_type_map = {
|
||||
"scene": ("Scene", False),
|
||||
"sculpt_object": ("Object", False),
|
||||
"selectable_objects": ("Object", True),
|
||||
"selected_asset_files": ("FileSelectEntry", True),
|
||||
"selected_bones": ("EditBone", True),
|
||||
"selected_editable_bones": ("EditBone", True),
|
||||
"selected_editable_fcurves": ("FCurve", True),
|
||||
|
@@ -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
|
||||
|
@@ -211,7 +211,6 @@ def list_render_passes(scene, srl):
|
||||
if crl.use_pass_shadow_catcher: yield ("Shadow Catcher", "RGB", 'COLOR')
|
||||
|
||||
# Debug passes.
|
||||
if crl.pass_debug_render_time: yield ("Debug Render Time", "X", 'VALUE')
|
||||
if crl.pass_debug_sample_count: yield ("Debug Sample Count", "X", 'VALUE')
|
||||
|
||||
# Cryptomatte passes.
|
||||
|
@@ -1197,12 +1197,6 @@ class CyclesCurveRenderSettings(bpy.types.PropertyGroup):
|
||||
|
||||
class CyclesRenderLayerSettings(bpy.types.PropertyGroup):
|
||||
|
||||
pass_debug_render_time: BoolProperty(
|
||||
name="Debug Render Time",
|
||||
description="Render time in milliseconds per sample and pixel",
|
||||
default=False,
|
||||
update=update_render_passes,
|
||||
)
|
||||
pass_debug_sample_count: BoolProperty(
|
||||
name="Debug Sample Count",
|
||||
description="Number of samples/camera rays per pixel",
|
||||
|
@@ -792,7 +792,6 @@ class CYCLES_RENDER_PT_passes_data(CyclesButtonsPanel, Panel):
|
||||
col.prop(view_layer, "use_pass_material_index")
|
||||
|
||||
col = layout.column(heading="Debug", align=True)
|
||||
col.prop(cycles_view_layer, "pass_debug_render_time", text="Render Time")
|
||||
col.prop(cycles_view_layer, "pass_debug_sample_count", text="Sample Count")
|
||||
|
||||
layout.prop(view_layer, "pass_alpha_threshold")
|
||||
|
@@ -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,10 +501,13 @@ 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;
|
||||
|
||||
/* All the files are handled.
|
||||
* Clear the list so that this session can be re-used by Persistent Data. */
|
||||
full_buffer_files_.clear();
|
||||
}
|
||||
|
||||
static PassType bake_type_to_pass(const string &bake_type_str, const int bake_filter)
|
||||
@@ -706,9 +612,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 +656,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 +764,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);
|
||||
|
@@ -545,8 +545,6 @@ static PassType get_blender_pass_type(BL::RenderPass &b_pass)
|
||||
MAP_PASS("Shadow Catcher", PASS_SHADOW_CATCHER);
|
||||
MAP_PASS("Noisy Shadow Catcher", PASS_SHADOW_CATCHER);
|
||||
|
||||
MAP_PASS("Debug Render Time", PASS_RENDER_TIME);
|
||||
|
||||
MAP_PASS("AdaptiveAuxBuffer", PASS_ADAPTIVE_AUX_BUFFER);
|
||||
MAP_PASS("Debug Sample Count", PASS_SAMPLE_COUNT);
|
||||
|
||||
@@ -604,10 +602,6 @@ void BlenderSync::sync_render_passes(BL::RenderLayer &b_rlay, BL::ViewLayer &b_v
|
||||
PointerRNA crl = RNA_pointer_get(&b_view_layer.ptr, "cycles");
|
||||
|
||||
/* Debug passes. */
|
||||
if (get_boolean(crl, "pass_debug_render_time")) {
|
||||
b_engine.add_pass("Debug Render Time", 1, "X", b_view_layer.name().c_str());
|
||||
pass_add(scene, PASS_RENDER_TIME, "Debug Render Time");
|
||||
}
|
||||
if (get_boolean(crl, "pass_debug_sample_count")) {
|
||||
b_engine.add_pass("Debug Sample Count", 1, "X", b_view_layer.name().c_str());
|
||||
pass_add(scene, PASS_SAMPLE_COUNT, "Debug Sample Count");
|
||||
|
@@ -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
|
||||
|
@@ -149,9 +149,6 @@ bool PassAccessor::get_render_tile_pixels(const RenderBuffers *render_buffers,
|
||||
/* Denoised passes store their final pixels, no need in special calculation. */
|
||||
get_pass_float(render_buffers, buffer_params, destination);
|
||||
}
|
||||
else if (type == PASS_RENDER_TIME) {
|
||||
/* TODO(sergey): Needs implementation. */
|
||||
}
|
||||
else if (type == PASS_DEPTH) {
|
||||
get_pass_depth(render_buffers, buffer_params, destination);
|
||||
}
|
||||
|
@@ -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;
|
||||
|
@@ -103,7 +103,7 @@ ccl_device_inline void shader_setup_from_ray(const KernelGlobals *ccl_restrict k
|
||||
|
||||
sd->flag |= kernel_tex_fetch(__shaders, (sd->shader & SHADER_MASK)).flags;
|
||||
|
||||
if (isect->object != OBJECT_NONE) {
|
||||
if (!(sd->object_flag & SD_OBJECT_TRANSFORM_APPLIED)) {
|
||||
/* instance transform */
|
||||
object_normal_transform_auto(kg, sd, &sd->N);
|
||||
object_normal_transform_auto(kg, sd, &sd->Ng);
|
||||
|
@@ -109,9 +109,17 @@ ccl_device bool integrator_init_from_bake(INTEGRATOR_STATE_ARGS,
|
||||
}
|
||||
|
||||
/* Position and normal on triangle. */
|
||||
const int object = kernel_data.bake.object_index;
|
||||
float3 P, Ng;
|
||||
int shader;
|
||||
triangle_point_normal(kg, kernel_data.bake.object_index, prim, u, v, &P, &Ng, &shader);
|
||||
triangle_point_normal(kg, object, prim, u, v, &P, &Ng, &shader);
|
||||
|
||||
const int object_flag = kernel_tex_fetch(__object_flag, object);
|
||||
if (!(object_flag & SD_OBJECT_TRANSFORM_APPLIED)) {
|
||||
Transform tfm = object_fetch_transform(kg, object, OBJECT_TRANSFORM);
|
||||
P = transform_point_auto(&tfm, P);
|
||||
}
|
||||
|
||||
if (kernel_data.film.pass_background != PASS_UNUSED) {
|
||||
/* Environment baking. */
|
||||
|
||||
@@ -130,8 +138,13 @@ ccl_device bool integrator_init_from_bake(INTEGRATOR_STATE_ARGS,
|
||||
}
|
||||
else {
|
||||
/* Surface baking. */
|
||||
const float3 N = (shader & SHADER_SMOOTH_NORMAL) ? triangle_smooth_normal(kg, Ng, prim, u, v) :
|
||||
Ng;
|
||||
float3 N = (shader & SHADER_SMOOTH_NORMAL) ? triangle_smooth_normal(kg, Ng, prim, u, v) : Ng;
|
||||
|
||||
if (!(object_flag & SD_OBJECT_TRANSFORM_APPLIED)) {
|
||||
Transform itfm = object_fetch_transform(kg, object, OBJECT_INVERSE_TRANSFORM);
|
||||
N = normalize(transform_direction_transposed(&itfm, N));
|
||||
Ng = normalize(transform_direction_transposed(&itfm, Ng));
|
||||
}
|
||||
|
||||
/* Setup ray. */
|
||||
Ray ray ccl_optional_struct_init;
|
||||
@@ -143,6 +156,12 @@ ccl_device bool integrator_init_from_bake(INTEGRATOR_STATE_ARGS,
|
||||
/* Setup differentials. */
|
||||
float3 dPdu, dPdv;
|
||||
triangle_dPdudv(kg, prim, &dPdu, &dPdv);
|
||||
if (!(object_flag & SD_OBJECT_TRANSFORM_APPLIED)) {
|
||||
Transform tfm = object_fetch_transform(kg, object, OBJECT_TRANSFORM);
|
||||
dPdu = transform_direction(&tfm, dPdu);
|
||||
dPdv = transform_direction(&tfm, dPdv);
|
||||
}
|
||||
|
||||
differential3 dP;
|
||||
dP.dx = dPdu * dudx + dPdv * dvdx;
|
||||
dP.dy = dPdu * dudy + dPdv * dvdy;
|
||||
|
@@ -123,7 +123,7 @@ ccl_device_forceinline void integrator_intersect_shader_next_kernel(
|
||||
#ifdef __SHADOW_CATCHER__
|
||||
const int object_flags = intersection_get_object_flags(kg, isect);
|
||||
if (kernel_shadow_catcher_split(INTEGRATOR_STATE_PASS, object_flags)) {
|
||||
if (kernel_data.film.use_approximate_shadow_catcher && !kernel_data.background.transparent) {
|
||||
if (kernel_data.film.pass_background != PASS_UNUSED && !kernel_data.background.transparent) {
|
||||
INTEGRATOR_STATE_WRITE(path, flag) |= PATH_RAY_SHADOW_CATCHER_BACKGROUND;
|
||||
|
||||
if (use_raytrace_kernel) {
|
||||
|
@@ -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];
|
||||
|
||||
|
@@ -360,7 +360,6 @@ typedef enum PassType {
|
||||
PASS_MATERIAL_ID,
|
||||
PASS_MOTION,
|
||||
PASS_MOTION_WEIGHT,
|
||||
PASS_RENDER_TIME,
|
||||
PASS_CRYPTOMATTE,
|
||||
PASS_AOV_COLOR,
|
||||
PASS_AOV_VALUE,
|
||||
|
@@ -110,6 +110,7 @@ ustring OSLRenderServices::u_curve_thickness("geom:curve_thickness");
|
||||
ustring OSLRenderServices::u_curve_length("geom:curve_length");
|
||||
ustring OSLRenderServices::u_curve_tangent_normal("geom:curve_tangent_normal");
|
||||
ustring OSLRenderServices::u_curve_random("geom:curve_random");
|
||||
ustring OSLRenderServices::u_normal_map_normal("geom:normal_map_normal");
|
||||
ustring OSLRenderServices::u_path_ray_length("path:ray_length");
|
||||
ustring OSLRenderServices::u_path_ray_depth("path:ray_depth");
|
||||
ustring OSLRenderServices::u_path_diffuse_depth("path:diffuse_depth");
|
||||
@@ -985,8 +986,18 @@ bool OSLRenderServices::get_object_standard_attribute(const KernelGlobals *kg,
|
||||
float3 f = curve_tangent_normal(kg, sd);
|
||||
return set_attribute_float3(f, type, derivatives, val);
|
||||
}
|
||||
else
|
||||
else if (name == u_normal_map_normal) {
|
||||
if (sd->type & PRIMITIVE_ALL_TRIANGLE) {
|
||||
float3 f = triangle_smooth_normal_unnormalized(kg, sd, sd->Ng, sd->prim, sd->u, sd->v);
|
||||
return set_attribute_float3(f, type, derivatives, val);
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool OSLRenderServices::get_background_attribute(const KernelGlobals *kg,
|
||||
|
@@ -297,6 +297,7 @@ class OSLRenderServices : public OSL::RendererServices {
|
||||
static ustring u_curve_length;
|
||||
static ustring u_curve_tangent_normal;
|
||||
static ustring u_curve_random;
|
||||
static ustring u_normal_map_normal;
|
||||
static ustring u_path_ray_length;
|
||||
static ustring u_path_ray_depth;
|
||||
static ustring u_path_diffuse_depth;
|
||||
|
@@ -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);
|
||||
}
|
@@ -45,7 +45,7 @@ shader node_normal_map(normal NormalIn = N,
|
||||
|
||||
// get _unnormalized_ interpolated normal and tangent
|
||||
if (getattribute(attr_name, tangent) && getattribute(attr_sign_name, tangent_sign) &&
|
||||
(!is_smooth || getattribute("geom:N", ninterp))) {
|
||||
(!is_smooth || getattribute("geom:normal_map_normal", ninterp))) {
|
||||
// apply normal map
|
||||
vector B = tangent_sign * cross(ninterp, tangent);
|
||||
Normal = normalize(mcolor[0] * tangent + mcolor[1] * B + mcolor[2] * ninterp);
|
||||
|
@@ -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
|
@@ -326,8 +326,6 @@ void Film::device_update(Device *device, DeviceScene *dscene, Scene *scene)
|
||||
kfilm->pass_bake_differential = kfilm->pass_stride;
|
||||
break;
|
||||
|
||||
case PASS_RENDER_TIME:
|
||||
break;
|
||||
case PASS_CRYPTOMATTE:
|
||||
kfilm->pass_cryptomatte = have_cryptomatte ?
|
||||
min(kfilm->pass_cryptomatte, kfilm->pass_stride) :
|
||||
|
@@ -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
|
@@ -89,7 +89,6 @@ const NodeEnum *Pass::get_type_enum()
|
||||
pass_type_enum.insert("material_id", PASS_MATERIAL_ID);
|
||||
pass_type_enum.insert("motion", PASS_MOTION);
|
||||
pass_type_enum.insert("motion_weight", PASS_MOTION_WEIGHT);
|
||||
pass_type_enum.insert("render_time", PASS_RENDER_TIME);
|
||||
pass_type_enum.insert("cryptomatte", PASS_CRYPTOMATTE);
|
||||
pass_type_enum.insert("aov_color", PASS_AOV_COLOR);
|
||||
pass_type_enum.insert("aov_value", PASS_AOV_VALUE);
|
||||
@@ -217,10 +216,6 @@ PassInfo Pass::get_info(const PassType type, const bool include_albedo)
|
||||
pass_info.num_components = 3;
|
||||
pass_info.use_exposure = false;
|
||||
break;
|
||||
case PASS_RENDER_TIME:
|
||||
/* This pass is handled entirely on the host side. */
|
||||
pass_info.num_components = 0;
|
||||
break;
|
||||
|
||||
case PASS_DIFFUSE_COLOR:
|
||||
case PASS_GLOSSY_COLOR:
|
||||
|
@@ -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.
|
||||
*
|
||||
|
@@ -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);
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -1112,7 +1112,7 @@ static tbool AssignRecur(const int piTriListIn[],
|
||||
|
||||
static tbool CompareSubGroups(const SSubGroup *pg1, const SSubGroup *pg2);
|
||||
static void QuickSort(int *pSortBuffer, int iLeft, int iRight, unsigned int uSeed);
|
||||
static STSpace EvalTspace(int face_indices[],
|
||||
static STSpace EvalTspace(const int face_indices[],
|
||||
const int iFaces,
|
||||
const int piTriListIn[],
|
||||
const STriInfo pTriInfos[],
|
||||
@@ -1292,7 +1292,7 @@ static tbool GenerateTSpaces(STSpace psTspace[],
|
||||
return TTRUE;
|
||||
}
|
||||
|
||||
static STSpace EvalTspace(int face_indices[],
|
||||
static STSpace EvalTspace(const int face_indices[],
|
||||
const int iFaces,
|
||||
const int piTriListIn[],
|
||||
const STriInfo pTriInfos[],
|
||||
|
@@ -62,11 +62,11 @@ const bTheme U_theme_default = {
|
||||
.roundness = 0.2f,
|
||||
},
|
||||
.wcol_radio = {
|
||||
.outline = RGBA(0x373737ff),
|
||||
.inner = RGBA(0x595959ff),
|
||||
.inner_sel = RGBA(0x5680c2e6),
|
||||
.item = RGBA(0xffffffff),
|
||||
.text = RGBA(0xe6e6e6ff),
|
||||
.outline = RGBA(0x2c2c2cff),
|
||||
.inner = RGBA(0x2c2c2cff),
|
||||
.inner_sel = RGBA(0x476da3ff),
|
||||
.item = RGBA(0x2c2c2cff),
|
||||
.text = RGBA(0xccccccff),
|
||||
.text_sel = RGBA(0xffffffff),
|
||||
.shadetop = 5,
|
||||
.shadedown = -5,
|
||||
@@ -74,9 +74,9 @@ const bTheme U_theme_default = {
|
||||
},
|
||||
.wcol_option = {
|
||||
.outline = RGBA(0x373737ff),
|
||||
.inner = RGBA(0x666666ff),
|
||||
.inner_sel = RGBA(0x5680c2e6),
|
||||
.item = RGBA(0xffffffff),
|
||||
.inner = RGBA(0x1f1f1fff),
|
||||
.inner_sel = RGBA(0x71a8ffe6),
|
||||
.item = RGBA(0x1f1f1fff),
|
||||
.text = RGBA(0xe6e6e6ff),
|
||||
.text_sel = RGBA(0xffffffff),
|
||||
.shadedown = -15,
|
||||
@@ -84,8 +84,8 @@ const bTheme U_theme_default = {
|
||||
},
|
||||
.wcol_toggle = {
|
||||
.outline = RGBA(0x373737ff),
|
||||
.inner = RGBA(0x595959ff),
|
||||
.inner_sel = RGBA(0x5680c2e6),
|
||||
.inner = RGBA(0x2c2c2cff),
|
||||
.inner_sel = RGBA(0x476da3ff),
|
||||
.item = RGBA(0x191919ff),
|
||||
.text = RGBA(0xe6e6e6ff),
|
||||
.text_sel = RGBA(0xffffffff),
|
||||
@@ -93,8 +93,8 @@ const bTheme U_theme_default = {
|
||||
},
|
||||
.wcol_num = {
|
||||
.outline = RGBA(0x444444ff),
|
||||
.inner = RGBA(0x595959ff),
|
||||
.inner_sel = RGBA(0x505050ff),
|
||||
.inner = RGBA(0x4f4f4fff),
|
||||
.inner_sel = RGBA(0x1f1f1fff),
|
||||
.item = RGBA(0x191919ff),
|
||||
.text = RGBA(0xe6e6e6ff),
|
||||
.text_sel = RGBA(0xffffffff),
|
||||
@@ -102,9 +102,9 @@ const bTheme U_theme_default = {
|
||||
},
|
||||
.wcol_numslider = {
|
||||
.outline = RGBA(0x444444ff),
|
||||
.inner = RGBA(0x595959ff),
|
||||
.inner = RGBA(0x2c2c2cff),
|
||||
.inner_sel = RGBA(0x505050ff),
|
||||
.item = RGBA(0x5680c2e6),
|
||||
.item = RGBA(0x5680c2ff),
|
||||
.text = RGBA(0xe6e6e6ff),
|
||||
.text_sel = RGBA(0xffffffff),
|
||||
.shaded = 1,
|
||||
@@ -112,9 +112,9 @@ const bTheme U_theme_default = {
|
||||
.roundness = 0.2f,
|
||||
},
|
||||
.wcol_tab = {
|
||||
.outline = RGBA(0x202020ff),
|
||||
.inner = RGBA(0x2b2b2bff),
|
||||
.inner_sel = RGBA(0x424242ff),
|
||||
.outline = RGBA(0x1a1a1aff),
|
||||
.inner = RGBA(0x1a1a1aff),
|
||||
.inner_sel = RGBA(0x2e2e2eff),
|
||||
.item = RGBA(0x2d2d2dff),
|
||||
.text = RGBA(0x989898ff),
|
||||
.text_sel = RGBA(0xffffffff),
|
||||
@@ -123,7 +123,7 @@ const bTheme U_theme_default = {
|
||||
.wcol_menu = {
|
||||
.outline = RGBA(0x444444ff),
|
||||
.inner = RGBA(0x2c2c2cff),
|
||||
.inner_sel = RGBA(0x696e76ff),
|
||||
.inner_sel = RGBA(0x476da3ff),
|
||||
.item = RGBA(0xd9d9d9ff),
|
||||
.text = RGBA(0xd9d9d9ff),
|
||||
.text_sel = RGBA(0xffffffff),
|
||||
@@ -133,8 +133,8 @@ const bTheme U_theme_default = {
|
||||
},
|
||||
.wcol_pulldown = {
|
||||
.outline = RGBA(0x4d4d4dff),
|
||||
.inner = RGBA(0x2e2e2ecc),
|
||||
.inner_sel = RGBA(0x5680c2e6),
|
||||
.inner = RGBA(0x00000033),
|
||||
.inner_sel = RGBA(0x476da3ff),
|
||||
.item = RGBA(0x727272ff),
|
||||
.text = RGBA(0xd9d9d9ff),
|
||||
.text_sel = RGBA(0xffffffff),
|
||||
@@ -144,7 +144,7 @@ const bTheme U_theme_default = {
|
||||
},
|
||||
.wcol_menu_back = {
|
||||
.outline = RGBA(0x19191aff),
|
||||
.inner = RGBA(0x1f1f1fef),
|
||||
.inner = RGBA(0x1a1a1aff),
|
||||
.inner_sel = RGBA(0x585858ff),
|
||||
.item = RGBA(0x727272ff),
|
||||
.text = RGBA(0xa5a5a5ff),
|
||||
@@ -154,7 +154,8 @@ const bTheme U_theme_default = {
|
||||
.roundness = 0.2f,
|
||||
},
|
||||
.wcol_menu_item = {
|
||||
.inner_sel = RGBA(0x5680c2e6),
|
||||
.outline = RGBA(0x37373700),
|
||||
.inner_sel = RGBA(0x476da3ff),
|
||||
.item = RGBA(0xffffff8f),
|
||||
.text = RGBA(0xe6e6e6ff),
|
||||
.text_sel = RGBA(0xffffffff),
|
||||
@@ -173,7 +174,7 @@ const bTheme U_theme_default = {
|
||||
.roundness = 0.2f,
|
||||
},
|
||||
.wcol_box = {
|
||||
.outline = RGBA(0x444444ff),
|
||||
.outline = RGBA(0x262626ff),
|
||||
.inner = RGBA(0x00000033),
|
||||
.inner_sel = RGBA(0x696e76ff),
|
||||
.item = RGBA(0x191919ff),
|
||||
@@ -237,7 +238,7 @@ const bTheme U_theme_default = {
|
||||
.widget_emboss = RGBA(0x00000026),
|
||||
.menu_shadow_fac = 0.3f,
|
||||
.menu_shadow_width = 4,
|
||||
.editor_outline = RGBA(0x1f1f1fff),
|
||||
.editor_outline = RGBA(0x1a1a1aff),
|
||||
.transparent_checker_primary = RGBA(0x333333ff),
|
||||
.transparent_checker_secondary = RGBA(0x262626ff),
|
||||
.transparent_checker_size = 8,
|
||||
@@ -262,11 +263,11 @@ const bTheme U_theme_default = {
|
||||
.icon_folder = RGBA(0xe3c16eff),
|
||||
},
|
||||
.space_properties = {
|
||||
.back = RGBA(0x42424200),
|
||||
.back = RGBA(0x2e2e2e00),
|
||||
.title = RGBA(0xd4d4d4ff),
|
||||
.text = RGBA(0xe6e6e6ff),
|
||||
.text_hi = RGBA(0xffffffff),
|
||||
.header = RGBA(0x424242ff),
|
||||
.header = RGBA(0x2e2e2eff),
|
||||
.header_text = RGBA(0xeeeeeeff),
|
||||
.header_text_hi = RGBA(0xffffffff),
|
||||
.tab_active = RGBA(0x4b4b4bff),
|
||||
@@ -277,24 +278,24 @@ const bTheme U_theme_default = {
|
||||
.button_title = RGBA(0xffffffff),
|
||||
.button_text = RGBA(0xe5e5e5ff),
|
||||
.button_text_hi = RGBA(0xffffffff),
|
||||
.navigation_bar = RGBA(0x232323ff),
|
||||
.navigation_bar = RGBA(0x1a1a1aff),
|
||||
.panelcolors = {
|
||||
.header = RGBA(0x424242ff),
|
||||
.header = RGBA(0x383838ff),
|
||||
.back = RGBA(0x383838ff),
|
||||
.sub_back = RGBA(0x00000024),
|
||||
.sub_back = RGBA(0x0000003e),
|
||||
},
|
||||
.active = RGBA(0x5680c2ff),
|
||||
.vertex_size = 3,
|
||||
.outline_width = 1,
|
||||
.facedot_size = 4,
|
||||
.match = RGBA(0x5680c2ff),
|
||||
.active = RGBA(0x5680c2ff),
|
||||
},
|
||||
.space_view3d = {
|
||||
.back = RGBA(0x393939ff),
|
||||
.back = RGBA(0x2c2c2cff),
|
||||
.title = RGBA(0xeeeeeeff),
|
||||
.text = RGBA(0xe6e6e6ff),
|
||||
.text_hi = RGBA(0xffffffff),
|
||||
.header = RGBA(0x42424200),
|
||||
.header = RGBA(0x2e2e2e00),
|
||||
.header_text = RGBA(0xeeeeeeff),
|
||||
.header_text_hi = RGBA(0xffffffff),
|
||||
.tab_active = RGBA(0x4b4b4bff),
|
||||
@@ -306,8 +307,8 @@ const bTheme U_theme_default = {
|
||||
.button_text = RGBA(0xe5e5e5ff),
|
||||
.button_text_hi = RGBA(0xffffffff),
|
||||
.panelcolors = {
|
||||
.header = RGBA(0x424242e6),
|
||||
.back = RGBA(0x333333f0),
|
||||
.header = RGBA(0x383838ff),
|
||||
.back = RGBA(0x383838ff),
|
||||
.sub_back = RGBA(0x0000003e),
|
||||
},
|
||||
.grid = RGBA(0x6666664d),
|
||||
@@ -398,8 +399,8 @@ const bTheme U_theme_default = {
|
||||
.button_text_hi = RGBA(0xffffffff),
|
||||
.execution_buts = RGBA(0x444444ff),
|
||||
.panelcolors = {
|
||||
.header = RGBA(0x4b4b4bff),
|
||||
.back = RGBA(0x404040ff),
|
||||
.header = RGBA(0x383838ff),
|
||||
.back = RGBA(0x383838ff),
|
||||
.sub_back = RGBA(0x0000003e),
|
||||
},
|
||||
.hilite = RGBA(0x4f76b3ff),
|
||||
@@ -429,8 +430,8 @@ const bTheme U_theme_default = {
|
||||
.list_text = RGBA(0xb8b8b8ff),
|
||||
.list_text_hi = RGBA(0xffaf29ff),
|
||||
.panelcolors = {
|
||||
.header = RGBA(0x424242cc),
|
||||
.back = RGBA(0x333333b3),
|
||||
.header = RGBA(0x383838ff),
|
||||
.back = RGBA(0x383838ff),
|
||||
.sub_back = RGBA(0x0000003e),
|
||||
},
|
||||
.shade1 = RGBA(0x96969600),
|
||||
@@ -480,8 +481,8 @@ const bTheme U_theme_default = {
|
||||
.button_text = RGBA(0xe5e5e5ff),
|
||||
.button_text_hi = RGBA(0xffffffff),
|
||||
.panelcolors = {
|
||||
.header = RGBA(0x424242cc),
|
||||
.back = RGBA(0x333333b3),
|
||||
.header = RGBA(0x383838ff),
|
||||
.back = RGBA(0x383838ff),
|
||||
.sub_back = RGBA(0x0000003e),
|
||||
},
|
||||
.vertex_size = 3,
|
||||
@@ -523,8 +524,8 @@ const bTheme U_theme_default = {
|
||||
.list_text = RGBA(0xb8b8b8ff),
|
||||
.list_text_hi = RGBA(0xffaf29ff),
|
||||
.panelcolors = {
|
||||
.header = RGBA(0x424242cc),
|
||||
.back = RGBA(0x333333b3),
|
||||
.header = RGBA(0x383838ff),
|
||||
.back = RGBA(0x383838ff),
|
||||
.sub_back = RGBA(0x0000003e),
|
||||
},
|
||||
.shade1 = RGBA(0xc0c0c000),
|
||||
@@ -583,8 +584,8 @@ const bTheme U_theme_default = {
|
||||
.list_text = RGBA(0xb8b8b8ff),
|
||||
.list_text_hi = RGBA(0xffaf29ff),
|
||||
.panelcolors = {
|
||||
.header = RGBA(0x424242cc),
|
||||
.back = RGBA(0x333333b3),
|
||||
.header = RGBA(0x383838ff),
|
||||
.back = RGBA(0x383838ff),
|
||||
.sub_back = RGBA(0x0000003e),
|
||||
},
|
||||
.shade1 = RGBA(0x96969600),
|
||||
@@ -633,8 +634,8 @@ const bTheme U_theme_default = {
|
||||
.button_text = RGBA(0xe5e5e5ff),
|
||||
.button_text_hi = RGBA(0xffffffff),
|
||||
.panelcolors = {
|
||||
.header = RGBA(0x424242cc),
|
||||
.back = RGBA(0x333333b3),
|
||||
.header = RGBA(0x383838ff),
|
||||
.back = RGBA(0x383838ff),
|
||||
.sub_back = RGBA(0x0000003e),
|
||||
},
|
||||
.shade1 = RGBA(0xa0a0a000),
|
||||
@@ -663,16 +664,16 @@ const bTheme U_theme_default = {
|
||||
.selected_strip = RGBA(0xff8f0dff),
|
||||
.gp_vertex_size = 3,
|
||||
.gp_vertex_select = RGBA(0xff8500ff),
|
||||
.row_alternate = RGBA(0xffffff0d),
|
||||
.anim_preview_range = RGBA(0xa14d0066),
|
||||
.metadatatext = RGBA(0xffffffff),
|
||||
.row_alternate = RGBA(0xffffff0d),
|
||||
},
|
||||
.space_image = {
|
||||
.back = RGBA(0x44444400),
|
||||
.back = RGBA(0x2e2e2e00),
|
||||
.title = RGBA(0xeeeeeeff),
|
||||
.text = RGBA(0xe6e6e6ff),
|
||||
.text_hi = RGBA(0xffffffff),
|
||||
.header = RGBA(0x424242ff),
|
||||
.header = RGBA(0x2e2e2eff),
|
||||
.header_text = RGBA(0xeeeeeeff),
|
||||
.header_text_hi = RGBA(0xffffffff),
|
||||
.tab_active = RGBA(0x4b4b4bff),
|
||||
@@ -684,8 +685,8 @@ const bTheme U_theme_default = {
|
||||
.button_text = RGBA(0xe5e5e5ff),
|
||||
.button_text_hi = RGBA(0xffffffff),
|
||||
.panelcolors = {
|
||||
.header = RGBA(0x424242cc),
|
||||
.back = RGBA(0x333333b3),
|
||||
.header = RGBA(0x383838ff),
|
||||
.back = RGBA(0x383838ff),
|
||||
.sub_back = RGBA(0x0000003e),
|
||||
},
|
||||
.grid = RGBA(0x505050ff),
|
||||
@@ -738,8 +739,8 @@ const bTheme U_theme_default = {
|
||||
.button_text = RGBA(0xe5e5e5ff),
|
||||
.button_text_hi = RGBA(0xffffffff),
|
||||
.panelcolors = {
|
||||
.header = RGBA(0x42424200),
|
||||
.back = RGBA(0x333333b3),
|
||||
.header = RGBA(0x383838ff),
|
||||
.back = RGBA(0x383838ff),
|
||||
.sub_back = RGBA(0x0000003e),
|
||||
},
|
||||
.shade2 = RGBA(0x5680c2e6),
|
||||
@@ -759,11 +760,11 @@ const bTheme U_theme_default = {
|
||||
.line_numbers = RGBA(0xd0d0d0ff),
|
||||
},
|
||||
.space_outliner = {
|
||||
.back = RGBA(0x28282800),
|
||||
.back = RGBA(0x25252500),
|
||||
.title = RGBA(0xffffffff),
|
||||
.text = RGBA(0xc3c3c3ff),
|
||||
.text_hi = RGBA(0xffffffff),
|
||||
.header = RGBA(0x454545ff),
|
||||
.header = RGBA(0x282828ff),
|
||||
.header_text = RGBA(0xeeeeeeff),
|
||||
.header_text_hi = RGBA(0xffffffff),
|
||||
.tab_active = RGBA(0x4b4b4bff),
|
||||
@@ -775,34 +776,34 @@ const bTheme U_theme_default = {
|
||||
.button_text = RGBA(0xe5e5e5ff),
|
||||
.button_text_hi = RGBA(0xffffffff),
|
||||
.panelcolors = {
|
||||
.header = RGBA(0x424242cc),
|
||||
.back = RGBA(0x333333b3),
|
||||
.header = RGBA(0x383838ff),
|
||||
.back = RGBA(0x383838ff),
|
||||
.sub_back = RGBA(0x0000003e),
|
||||
},
|
||||
.active = RGBA(0x3b5689ff),
|
||||
.active = RGBA(0x4d341fff),
|
||||
.vertex_size = 3,
|
||||
.outline_width = 1,
|
||||
.facedot_size = 4,
|
||||
.match = RGBA(0x337f334c),
|
||||
.selected_highlight = RGBA(0x223a5bff),
|
||||
.match = RGBA(0x1466144c),
|
||||
.selected_highlight = RGBA(0x4d433eff),
|
||||
.selected_object = RGBA(0xe96a00ff),
|
||||
.active_object = RGBA(0xffaf29ff),
|
||||
.edited_object = RGBA(0x00806266),
|
||||
.row_alternate = RGBA(0xffffff07),
|
||||
.row_alternate = RGBA(0xffffff05),
|
||||
},
|
||||
.space_node = {
|
||||
.back = RGBA(0x23232300),
|
||||
.title = RGBA(0xeeeeeeff),
|
||||
.text = RGBA(0xe6e6e6ff),
|
||||
.text_hi = RGBA(0xffffffff),
|
||||
.header = RGBA(0x424242ff),
|
||||
.header = RGBA(0x2e2e2eff),
|
||||
.header_text = RGBA(0xeeeeeeff),
|
||||
.header_text_hi = RGBA(0xffffffff),
|
||||
.tab_active = RGBA(0x4b4b4bff),
|
||||
.tab_inactive = RGBA(0x2b2b2bff),
|
||||
.tab_back = RGBA(0x232323ff),
|
||||
.tab_outline = RGBA(0x232323ff),
|
||||
.button = RGBA(0x42424200),
|
||||
.button = RGBA(0x232323ff),
|
||||
.button_title = RGBA(0xffffffff),
|
||||
.button_text = RGBA(0xe5e5e5ff),
|
||||
.button_text_hi = RGBA(0xffffffff),
|
||||
@@ -811,8 +812,8 @@ const bTheme U_theme_default = {
|
||||
.list_text = RGBA(0xccccccff),
|
||||
.list_text_hi = RGBA(0xffffffff),
|
||||
.panelcolors = {
|
||||
.header = RGBA(0x424242cc),
|
||||
.back = RGBA(0x333333b3),
|
||||
.header = RGBA(0x383838ff),
|
||||
.back = RGBA(0x383838ff),
|
||||
.sub_back = RGBA(0x0000003e),
|
||||
},
|
||||
.shade2 = RGBA(0x7f707064),
|
||||
@@ -851,11 +852,11 @@ const bTheme U_theme_default = {
|
||||
.gp_vertex_select = RGBA(0xff8500ff),
|
||||
},
|
||||
.space_preferences = {
|
||||
.back = RGBA(0x42424200),
|
||||
.title = RGBA(0xeeeeeeff),
|
||||
.back = RGBA(0x2e2e2e00),
|
||||
.title = RGBA(0xd4d4d4ff),
|
||||
.text = RGBA(0xe6e6e6ff),
|
||||
.text_hi = RGBA(0xffffffff),
|
||||
.header = RGBA(0x424242ff),
|
||||
.header = RGBA(0x2e2e2eff),
|
||||
.header_text = RGBA(0xeeeeeeff),
|
||||
.header_text_hi = RGBA(0xffffffff),
|
||||
.tab_active = RGBA(0x4b4b4bff),
|
||||
@@ -866,11 +867,11 @@ const bTheme U_theme_default = {
|
||||
.button_title = RGBA(0xffffffff),
|
||||
.button_text = RGBA(0xe5e5e5ff),
|
||||
.button_text_hi = RGBA(0xffffffff),
|
||||
.navigation_bar = RGBA(0x4b4b4bff),
|
||||
.execution_buts = RGBA(0x4b4b4bff),
|
||||
.navigation_bar = RGBA(0x383838ff),
|
||||
.execution_buts = RGBA(0x383838ff),
|
||||
.panelcolors = {
|
||||
.header = RGBA(0x42424200),
|
||||
.back = RGBA(0x333333b3),
|
||||
.header = RGBA(0x383838ff),
|
||||
.back = RGBA(0x383838ff),
|
||||
.sub_back = RGBA(0x0000003e),
|
||||
},
|
||||
.vertex_size = 3,
|
||||
@@ -894,8 +895,8 @@ const bTheme U_theme_default = {
|
||||
.button_text = RGBA(0xe5e5e5ff),
|
||||
.button_text_hi = RGBA(0xffffffff),
|
||||
.panelcolors = {
|
||||
.header = RGBA(0x424242cc),
|
||||
.back = RGBA(0x333333b3),
|
||||
.header = RGBA(0x383838ff),
|
||||
.back = RGBA(0x383838ff),
|
||||
.sub_back = RGBA(0x0000003e),
|
||||
},
|
||||
.console_output = RGBA(0x71a8ffff),
|
||||
@@ -928,8 +929,8 @@ const bTheme U_theme_default = {
|
||||
.list_text = RGBA(0x000000ff),
|
||||
.list_text_hi = RGBA(0xffffffff),
|
||||
.panelcolors = {
|
||||
.header = RGBA(0x424242cc),
|
||||
.back = RGBA(0x333333b3),
|
||||
.header = RGBA(0x383838ff),
|
||||
.back = RGBA(0x383838ff),
|
||||
.sub_back = RGBA(0x0000003e),
|
||||
},
|
||||
.grid = RGBA(0x424242ff),
|
||||
@@ -961,11 +962,11 @@ const bTheme U_theme_default = {
|
||||
.metadatatext = RGBA(0xffffffff),
|
||||
},
|
||||
.space_topbar = {
|
||||
.back = RGBA(0x42424200),
|
||||
.back = RGBA(0x1a1a1a00),
|
||||
.title = RGBA(0xffffffff),
|
||||
.text = RGBA(0xe6e6e6ff),
|
||||
.text_hi = RGBA(0xffffffff),
|
||||
.header = RGBA(0x232323ff),
|
||||
.header = RGBA(0x1a1a1aff),
|
||||
.header_text = RGBA(0xeeeeeeff),
|
||||
.header_text_hi = RGBA(0xffffffff),
|
||||
.tab_active = RGBA(0x4b4b4bff),
|
||||
@@ -977,8 +978,8 @@ const bTheme U_theme_default = {
|
||||
.button_text = RGBA(0xe5e5e5ff),
|
||||
.button_text_hi = RGBA(0xffffffff),
|
||||
.panelcolors = {
|
||||
.header = RGBA(0x424242cc),
|
||||
.back = RGBA(0x333333b3),
|
||||
.header = RGBA(0x383838ff),
|
||||
.back = RGBA(0x383838ff),
|
||||
.sub_back = RGBA(0x0000003e),
|
||||
},
|
||||
.vertex_size = 3,
|
||||
@@ -987,12 +988,12 @@ const bTheme U_theme_default = {
|
||||
.gp_vertex_size = 3,
|
||||
},
|
||||
.space_statusbar = {
|
||||
.back = RGBA(0x2e2e2e00),
|
||||
.back = RGBA(0x1a1a1a00),
|
||||
.title = RGBA(0xffffffff),
|
||||
.text = RGBA(0x838383ff),
|
||||
.text_hi = RGBA(0xffffffff),
|
||||
.header = RGBA(0x303030ff),
|
||||
.header_text = RGBA(0xaaaaaaff),
|
||||
.header = RGBA(0x1a1a1aff),
|
||||
.header_text = RGBA(0x696969ff),
|
||||
.header_text_hi = RGBA(0xffffffff),
|
||||
.tab_active = RGBA(0x4b4b4bff),
|
||||
.tab_inactive = RGBA(0x2b2b2bff),
|
||||
@@ -1001,8 +1002,8 @@ const bTheme U_theme_default = {
|
||||
.button = RGBA(0x353535ff),
|
||||
.button_text_hi = RGBA(0xffffffff),
|
||||
.panelcolors = {
|
||||
.header = RGBA(0x424242cc),
|
||||
.back = RGBA(0x333333b3),
|
||||
.header = RGBA(0x383838ff),
|
||||
.back = RGBA(0x383838ff),
|
||||
.sub_back = RGBA(0x0000003e),
|
||||
},
|
||||
.vertex_size = 3,
|
||||
@@ -1026,11 +1027,16 @@ const bTheme U_theme_default = {
|
||||
.button_title = RGBA(0xffffffff),
|
||||
.button_text = RGBA(0xe5e5e5ff),
|
||||
.button_text_hi = RGBA(0xffffffff),
|
||||
.list = RGBA(0x424242ff),
|
||||
.list_title = RGBA(0xc3c3c3ff),
|
||||
.list_text = RGBA(0xc3c3c3ff),
|
||||
.list_text_hi = RGBA(0x00ffffff),
|
||||
.panelcolors = {
|
||||
.header = RGBA(0x424242cc),
|
||||
.back = RGBA(0x333333b3),
|
||||
.header = RGBA(0x383838ff),
|
||||
.back = RGBA(0x383838ff),
|
||||
.sub_back = RGBA(0x0000003e),
|
||||
},
|
||||
.hilite = RGBA(0x80808080),
|
||||
.active = RGBA(0x3b5689ff),
|
||||
.vertex_size = 3,
|
||||
.outline_width = 1,
|
||||
@@ -1041,11 +1047,6 @@ const bTheme U_theme_default = {
|
||||
.active_object = RGBA(0xffaf29ff),
|
||||
.edited_object = RGBA(0x00806266),
|
||||
.row_alternate = RGBA(0xffffff07),
|
||||
.list = RGBA(0x424242ff),
|
||||
.list_title = RGBA(0xc3c3c3ff),
|
||||
.list_text = RGBA(0xc3c3c3ff),
|
||||
.list_text_hi = RGBA(0xffffff),
|
||||
.hilite = RGBA(0x80808080),
|
||||
},
|
||||
.tarm = {
|
||||
{
|
||||
|
@@ -378,7 +378,15 @@ def dump_rna_messages(msgs, reports, settings, verbose=False):
|
||||
if cls in blacklist_rna_class:
|
||||
return cls.__name__
|
||||
cls_id = ""
|
||||
bl_rna = cls.bl_rna
|
||||
bl_rna = getattr(cls, "bl_rna", None)
|
||||
# It seems that py-defined 'wrappers' RNA classes (like `MeshEdge` in `bpy_types.py`) need to be accessed
|
||||
# once from `bpy.types` before they have a valid `bl_rna` member.
|
||||
# Weirdly enough, this is only triggered on release builds, debug builds somehow do not have that issue.
|
||||
if bl_rna is None:
|
||||
if getattr(bpy.types, cls.__name__, None) is not None:
|
||||
bl_rna = getattr(cls, "bl_rna", None)
|
||||
if bl_rna is None:
|
||||
raise TypeError("Unknown RNA class")
|
||||
while bl_rna:
|
||||
cls_id = bl_rna.identifier + "." + cls_id
|
||||
bl_rna = bl_rna.base
|
||||
|
@@ -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:
|
||||
|
@@ -176,6 +176,7 @@ class SpellChecker:
|
||||
"precalculate",
|
||||
"precomputing",
|
||||
"prefetch",
|
||||
"prefilter", "prefiltering",
|
||||
"preload",
|
||||
"premultiply", "premultiplied",
|
||||
"prepass",
|
||||
@@ -225,6 +226,7 @@ class SpellChecker:
|
||||
"subpath",
|
||||
"subsize",
|
||||
"substep", "substeps",
|
||||
"substring",
|
||||
"targetless",
|
||||
"textbox", "textboxes",
|
||||
"tilemode",
|
||||
@@ -731,6 +733,7 @@ class SpellChecker:
|
||||
"tma",
|
||||
"ui",
|
||||
"unix",
|
||||
"uuid",
|
||||
"vbo", "vbos",
|
||||
"vr",
|
||||
"wxyz",
|
||||
|
@@ -77,10 +77,10 @@
|
||||
</wcol_toolbar_item>
|
||||
<wcol_radio>
|
||||
<ThemeWidgetColors
|
||||
outline="#434343"
|
||||
outline="#3b3b3b"
|
||||
inner="#3b3b3bff"
|
||||
inner_sel="#5680c2e6"
|
||||
item="#ffffffff"
|
||||
item="#3b3b3bff"
|
||||
text="#d9d9d9"
|
||||
text_sel="#ffffff"
|
||||
show_shaded="FALSE"
|
||||
@@ -257,7 +257,7 @@
|
||||
</wcol_tooltip>
|
||||
<wcol_menu_item>
|
||||
<ThemeWidgetColors
|
||||
outline="#000000"
|
||||
outline="#a6a6a6"
|
||||
inner="#00000000"
|
||||
inner_sel="#5680c2e6"
|
||||
item="#ffffff8f"
|
||||
@@ -879,7 +879,7 @@
|
||||
>
|
||||
<panelcolors>
|
||||
<ThemePanelColors
|
||||
header="#b3b3b300"
|
||||
header="#a3a3a3cc"
|
||||
back="#a3a3a3cc"
|
||||
sub_back="#00000024"
|
||||
>
|
||||
@@ -1054,17 +1054,17 @@
|
||||
<ThemeInfo
|
||||
info_selected="#6080ff"
|
||||
info_selected_text="#000000"
|
||||
info_error="#FF0038ff"
|
||||
info_error="#ff0038ff"
|
||||
info_error_text="#000000"
|
||||
info_warning="#FFE900ff"
|
||||
info_warning="#ffe900ff"
|
||||
info_warning_text="#000000"
|
||||
info_info="#0068B3ff"
|
||||
info_info="#0068b3ff"
|
||||
info_info_text="#000000"
|
||||
info_debug="#B30095ff"
|
||||
info_debug="#b30095ff"
|
||||
info_debug_text="#000000"
|
||||
info_property="#44B300ff"
|
||||
info_property="#44b300ff"
|
||||
info_property_text="#000000"
|
||||
info_operator="#44B300ff"
|
||||
info_operator="#44b300ff"
|
||||
info_operator_text="#000000"
|
||||
>
|
||||
<space>
|
||||
@@ -1350,6 +1350,15 @@
|
||||
</panelcolors>
|
||||
</ThemeSpaceGeneric>
|
||||
</space>
|
||||
<space_list>
|
||||
<ThemeSpaceListGeneric
|
||||
list="#424242"
|
||||
list_title="#c3c3c3"
|
||||
list_text="#c3c3c3"
|
||||
list_text_hi="#00ffff"
|
||||
>
|
||||
</ThemeSpaceListGeneric>
|
||||
</space_list>
|
||||
</ThemeSpreadsheet>
|
||||
</spreadsheet>
|
||||
<bone_color_sets>
|
||||
|
@@ -71,7 +71,7 @@ class Params:
|
||||
# (derived from other settings).
|
||||
#
|
||||
# This case needs to be checked often,
|
||||
# Shorthand for: `(params.use_fallback_tool if params.select_mouse == 'RIGHT' else False)`.
|
||||
# Shorthand for: `(params.use_fallback_tool if params.select_mouse == 'RIGHTMOUSE' else False)`.
|
||||
"use_fallback_tool_rmb",
|
||||
# Shorthand for: `('CLICK' if params.use_fallback_tool_rmb else params.select_mouse_value)`.
|
||||
"select_mouse_value_fallback",
|
||||
@@ -195,7 +195,7 @@ class Params:
|
||||
self.use_file_single_click = use_file_single_click
|
||||
|
||||
# Convenience variables:
|
||||
self.use_fallback_tool_rmb = self.use_fallback_tool if self.select_mouse == 'RIGHT' else False
|
||||
self.use_fallback_tool_rmb = self.use_fallback_tool if select_mouse == 'RIGHT' else False
|
||||
self.select_mouse_value_fallback = 'CLICK' if self.use_fallback_tool_rmb else self.select_mouse_value
|
||||
self.pie_value = 'CLICK_DRAG' if use_pie_click_drag else 'PRESS'
|
||||
self.tool_tweak_event = {"type": self.tool_tweak, "value": 'ANY'}
|
||||
@@ -4312,7 +4312,7 @@ def km_pose(params):
|
||||
("pose.push", {"type": 'E', "value": 'PRESS', "ctrl": True}, None),
|
||||
("pose.relax", {"type": 'E', "value": 'PRESS', "alt": True}, None),
|
||||
("pose.breakdown", {"type": 'E', "value": 'PRESS', "shift": True}, None),
|
||||
("pose.blend_to_neighbour", {"type": 'E', "value": 'PRESS', "shift": True, "alt": True}, None),
|
||||
("pose.blend_to_neighbor", {"type": 'E', "value": 'PRESS', "shift": True, "alt": True}, None),
|
||||
op_menu("VIEW3D_MT_pose_propagate", {"type": 'P', "value": 'PRESS', "alt": True}),
|
||||
*(
|
||||
(("object.hide_collection",
|
||||
|
@@ -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)
|
||||
@@ -1003,16 +1048,23 @@ class SequencerButtonsPanel_Output:
|
||||
return cls.has_preview(context)
|
||||
|
||||
|
||||
class SEQUENCER_PT_color_tag_picker(Panel):
|
||||
bl_label = "Color Tag"
|
||||
class SequencerColorTagPicker:
|
||||
bl_space_type = 'SEQUENCE_EDITOR'
|
||||
bl_region_type = 'UI'
|
||||
bl_category = "Strip"
|
||||
bl_options = {'HIDE_HEADER', 'INSTANCED'}
|
||||
|
||||
@staticmethod
|
||||
def has_sequencer(context):
|
||||
return (context.space_data.view_type in {'SEQUENCER', 'SEQUENCER_PREVIEW'})
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.active_sequence_strip is not None
|
||||
return cls.has_sequencer(context) and context.active_sequence_strip is not None
|
||||
|
||||
|
||||
class SEQUENCER_PT_color_tag_picker(SequencerColorTagPicker, Panel):
|
||||
bl_label = "Color Tag"
|
||||
bl_category = "Strip"
|
||||
bl_options = {'HIDE_HEADER', 'INSTANCED'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
@@ -1024,13 +1076,9 @@ class SEQUENCER_PT_color_tag_picker(Panel):
|
||||
row.operator("sequencer.strip_color_tag_set", icon=icon).color = 'COLOR_%02d' % i
|
||||
|
||||
|
||||
class SEQUENCER_MT_color_tag_picker(Menu):
|
||||
class SEQUENCER_MT_color_tag_picker(SequencerColorTagPicker, Menu):
|
||||
bl_label = "Set Color Tag"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.active_sequence_strip is not None
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
|
@@ -3443,7 +3443,7 @@ class VIEW3D_MT_pose_slide(Menu):
|
||||
layout.operator("pose.push")
|
||||
layout.operator("pose.relax")
|
||||
layout.operator("pose.breakdown")
|
||||
layout.operator("pose.blend_to_neighbour")
|
||||
layout.operator("pose.blend_to_neighbor")
|
||||
|
||||
|
||||
class VIEW3D_MT_pose_propagate(Menu):
|
||||
@@ -3596,7 +3596,7 @@ class VIEW3D_MT_pose_context_menu(Menu):
|
||||
layout.operator("pose.push")
|
||||
layout.operator("pose.relax")
|
||||
layout.operator("pose.breakdown")
|
||||
layout.operator("pose.blend_to_neighbour")
|
||||
layout.operator("pose.blend_to_neighbor")
|
||||
|
||||
layout.separator()
|
||||
|
||||
|
@@ -279,6 +279,7 @@ shader_node_categories = [
|
||||
]),
|
||||
ShaderNodeCategory("SH_NEW_CONVERTOR", "Converter", items=[
|
||||
NodeItem("ShaderNodeMapRange"),
|
||||
NodeItem("ShaderNodeFloatCurve"),
|
||||
NodeItem("ShaderNodeClamp"),
|
||||
NodeItem("ShaderNodeMath"),
|
||||
NodeItem("ShaderNodeValToRGB"),
|
||||
@@ -524,9 +525,14 @@ geometry_node_categories = [
|
||||
NodeItem("GeometryNodeCurveFill"),
|
||||
NodeItem("GeometryNodeCurveTrim"),
|
||||
NodeItem("GeometryNodeCurveLength"),
|
||||
NodeItem("GeometryNodeCurveSplineType"),
|
||||
NodeItem("GeometryNodeSplineLength"),
|
||||
NodeItem("GeometryNodeCurveSubdivide"),
|
||||
NodeItem("GeometryNodeCurveParameter"),
|
||||
NodeItem("GeometryNodeCurveSetHandles"),
|
||||
NodeItem("GeometryNodeInputTangent"),
|
||||
NodeItem("GeometryNodeCurveSample"),
|
||||
NodeItem("GeometryNodeCurveHandleTypeSelection"),
|
||||
NodeItem("GeometryNodeCurveFillet"),
|
||||
NodeItem("GeometryNodeCurveReverse"),
|
||||
]),
|
||||
@@ -615,9 +621,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"),
|
||||
@@ -639,6 +647,7 @@ geometry_node_categories = [
|
||||
GeometryNodeCategory("GEO_VOLUME", "Volume", items=[
|
||||
NodeItem("GeometryNodeLegacyPointsToVolume", poll=geometry_nodes_legacy_poll),
|
||||
|
||||
NodeItem("GeometryNodePointsToVolume"),
|
||||
NodeItem("GeometryNodeVolumeToMesh"),
|
||||
]),
|
||||
GeometryNodeCategory("GEO_GROUP", "Group", items=node_group_items),
|
||||
|
@@ -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();
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -166,13 +181,15 @@ class AssetCatalogTreeItem {
|
||||
|
||||
AssetCatalogTreeItem(StringRef name,
|
||||
CatalogID catalog_id,
|
||||
StringRef simple_name,
|
||||
const AssetCatalogTreeItem *parent = nullptr);
|
||||
|
||||
CatalogID get_catalog_id() const;
|
||||
StringRef get_name() const;
|
||||
StringRefNull get_simple_name() const;
|
||||
StringRefNull 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;
|
||||
|
||||
@@ -186,6 +203,8 @@ class AssetCatalogTreeItem {
|
||||
/** The user visible name of this component. */
|
||||
CatalogPathComponent name_;
|
||||
CatalogID catalog_id_;
|
||||
/** Copy of #AssetCatalog::simple_name. */
|
||||
std::string simple_name_;
|
||||
|
||||
/** Pointer back to the parent item. Used to reconstruct the hierarchy from an item (e.g. to
|
||||
* build a path). */
|
||||
@@ -280,10 +299,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 +316,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 +345,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" {
|
||||
|
@@ -42,66 +42,21 @@ class AttributeIDRef {
|
||||
const AnonymousAttributeID *anonymous_id_ = nullptr;
|
||||
|
||||
public:
|
||||
AttributeIDRef() = default;
|
||||
AttributeIDRef();
|
||||
AttributeIDRef(StringRef name);
|
||||
AttributeIDRef(StringRefNull name);
|
||||
AttributeIDRef(const char *name);
|
||||
AttributeIDRef(const std::string &name);
|
||||
AttributeIDRef(const AnonymousAttributeID *anonymous_id);
|
||||
|
||||
AttributeIDRef(StringRef name) : name_(name)
|
||||
{
|
||||
}
|
||||
|
||||
AttributeIDRef(StringRefNull name) : name_(name)
|
||||
{
|
||||
}
|
||||
|
||||
AttributeIDRef(const char *name) : name_(name)
|
||||
{
|
||||
}
|
||||
|
||||
AttributeIDRef(const std::string &name) : name_(name)
|
||||
{
|
||||
}
|
||||
|
||||
/* The anonymous id is only borrowed, the caller has to keep a reference to it. */
|
||||
AttributeIDRef(const AnonymousAttributeID *anonymous_id) : anonymous_id_(anonymous_id)
|
||||
{
|
||||
}
|
||||
|
||||
operator bool() const
|
||||
{
|
||||
return this->is_named() || this->is_anonymous();
|
||||
}
|
||||
|
||||
friend bool operator==(const AttributeIDRef &a, const AttributeIDRef &b)
|
||||
{
|
||||
return a.anonymous_id_ == b.anonymous_id_ && a.name_ == b.name_;
|
||||
}
|
||||
|
||||
uint64_t hash() const
|
||||
{
|
||||
return get_default_hash_2(name_, anonymous_id_);
|
||||
}
|
||||
|
||||
bool is_named() const
|
||||
{
|
||||
return !name_.is_empty();
|
||||
}
|
||||
|
||||
bool is_anonymous() const
|
||||
{
|
||||
return anonymous_id_ != nullptr;
|
||||
}
|
||||
|
||||
StringRef name() const
|
||||
{
|
||||
BLI_assert(this->is_named());
|
||||
return name_;
|
||||
}
|
||||
|
||||
const AnonymousAttributeID &anonymous_id() const
|
||||
{
|
||||
BLI_assert(this->is_anonymous());
|
||||
return *anonymous_id_;
|
||||
}
|
||||
operator bool() const;
|
||||
uint64_t hash() const;
|
||||
bool is_named() const;
|
||||
bool is_anonymous() const;
|
||||
StringRef name() const;
|
||||
const AnonymousAttributeID &anonymous_id() const;
|
||||
|
||||
friend bool operator==(const AttributeIDRef &a, const AttributeIDRef &b);
|
||||
friend std::ostream &operator<<(std::ostream &stream, const AttributeIDRef &attribute_id);
|
||||
};
|
||||
|
||||
@@ -259,73 +214,26 @@ class OutputAttribute {
|
||||
bool save_has_been_called_ = false;
|
||||
|
||||
public:
|
||||
OutputAttribute() = default;
|
||||
|
||||
OutputAttribute();
|
||||
OutputAttribute(OutputAttribute &&other);
|
||||
OutputAttribute(GVMutableArrayPtr varray,
|
||||
AttributeDomain domain,
|
||||
SaveFn save,
|
||||
const bool ignore_old_values)
|
||||
: varray_(std::move(varray)),
|
||||
domain_(domain),
|
||||
save_(std::move(save)),
|
||||
ignore_old_values_(ignore_old_values)
|
||||
{
|
||||
}
|
||||
|
||||
OutputAttribute(OutputAttribute &&other) = default;
|
||||
const bool ignore_old_values);
|
||||
|
||||
~OutputAttribute();
|
||||
|
||||
operator bool() const
|
||||
{
|
||||
return varray_.get() != nullptr;
|
||||
}
|
||||
operator bool() const;
|
||||
|
||||
GVMutableArray &operator*()
|
||||
{
|
||||
return *varray_;
|
||||
}
|
||||
GVMutableArray &operator*();
|
||||
GVMutableArray *operator->();
|
||||
GVMutableArray &varray();
|
||||
AttributeDomain domain() const;
|
||||
const CPPType &cpp_type() const;
|
||||
CustomDataType custom_data_type() const;
|
||||
|
||||
GVMutableArray *operator->()
|
||||
{
|
||||
return varray_.get();
|
||||
}
|
||||
|
||||
GVMutableArray &varray()
|
||||
{
|
||||
return *varray_;
|
||||
}
|
||||
|
||||
AttributeDomain domain() const
|
||||
{
|
||||
return domain_;
|
||||
}
|
||||
|
||||
const CPPType &cpp_type() const
|
||||
{
|
||||
return varray_->type();
|
||||
}
|
||||
|
||||
CustomDataType custom_data_type() const
|
||||
{
|
||||
return cpp_type_to_custom_data_type(this->cpp_type());
|
||||
}
|
||||
|
||||
fn::GMutableSpan as_span()
|
||||
{
|
||||
if (!optional_span_varray_) {
|
||||
const bool materialize_old_values = !ignore_old_values_;
|
||||
optional_span_varray_ = std::make_unique<fn::GVMutableArray_GSpan>(*varray_,
|
||||
materialize_old_values);
|
||||
}
|
||||
fn::GVMutableArray_GSpan &span_varray = *optional_span_varray_;
|
||||
return span_varray;
|
||||
}
|
||||
|
||||
template<typename T> MutableSpan<T> as_span()
|
||||
{
|
||||
return this->as_span().typed<T>();
|
||||
}
|
||||
fn::GMutableSpan as_span();
|
||||
template<typename T> MutableSpan<T> as_span();
|
||||
|
||||
void save();
|
||||
};
|
||||
@@ -336,18 +244,32 @@ class OutputAttribute {
|
||||
template<typename T> class OutputAttribute_Typed {
|
||||
private:
|
||||
OutputAttribute attribute_;
|
||||
std::optional<fn::GVMutableArray_Typed<T>> optional_varray_;
|
||||
std::unique_ptr<fn::GVMutableArray_Typed<T>> optional_varray_;
|
||||
VMutableArray<T> *varray_ = nullptr;
|
||||
|
||||
public:
|
||||
OutputAttribute_Typed();
|
||||
OutputAttribute_Typed(OutputAttribute attribute) : attribute_(std::move(attribute))
|
||||
{
|
||||
if (attribute_) {
|
||||
optional_varray_.emplace(attribute_.varray());
|
||||
optional_varray_ = std::make_unique<fn::GVMutableArray_Typed<T>>(attribute_.varray());
|
||||
varray_ = &**optional_varray_;
|
||||
}
|
||||
}
|
||||
|
||||
OutputAttribute_Typed(OutputAttribute_Typed &&other);
|
||||
~OutputAttribute_Typed();
|
||||
|
||||
OutputAttribute_Typed &operator=(OutputAttribute_Typed &&other)
|
||||
{
|
||||
if (this == &other) {
|
||||
return *this;
|
||||
}
|
||||
this->~OutputAttribute_Typed();
|
||||
new (this) OutputAttribute_Typed(std::move(other));
|
||||
return *this;
|
||||
}
|
||||
|
||||
operator bool() const
|
||||
{
|
||||
return varray_ != nullptr;
|
||||
@@ -394,6 +316,13 @@ template<typename T> class OutputAttribute_Typed {
|
||||
}
|
||||
};
|
||||
|
||||
/* These are not defined in the class directly, because when defining them there, the external
|
||||
* template instantiation does not work, resulting in longer compile times. */
|
||||
template<typename T> inline OutputAttribute_Typed<T>::OutputAttribute_Typed() = default;
|
||||
template<typename T>
|
||||
inline OutputAttribute_Typed<T>::OutputAttribute_Typed(OutputAttribute_Typed &&other) = default;
|
||||
template<typename T> inline OutputAttribute_Typed<T>::~OutputAttribute_Typed() = default;
|
||||
|
||||
/**
|
||||
* A basic container around DNA CustomData so that its users
|
||||
* don't have to implement special copy and move constructors.
|
||||
@@ -444,4 +373,139 @@ class CustomDataAttributes {
|
||||
const AttributeDomain domain) const;
|
||||
};
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* #AttributeIDRef inline methods.
|
||||
*/
|
||||
|
||||
inline AttributeIDRef::AttributeIDRef() = default;
|
||||
|
||||
inline AttributeIDRef::AttributeIDRef(StringRef name) : name_(name)
|
||||
{
|
||||
}
|
||||
|
||||
inline AttributeIDRef::AttributeIDRef(StringRefNull name) : name_(name)
|
||||
{
|
||||
}
|
||||
|
||||
inline AttributeIDRef::AttributeIDRef(const char *name) : name_(name)
|
||||
{
|
||||
}
|
||||
|
||||
inline AttributeIDRef::AttributeIDRef(const std::string &name) : name_(name)
|
||||
{
|
||||
}
|
||||
|
||||
/* The anonymous id is only borrowed, the caller has to keep a reference to it. */
|
||||
inline AttributeIDRef::AttributeIDRef(const AnonymousAttributeID *anonymous_id)
|
||||
: anonymous_id_(anonymous_id)
|
||||
{
|
||||
}
|
||||
|
||||
inline bool operator==(const AttributeIDRef &a, const AttributeIDRef &b)
|
||||
{
|
||||
return a.anonymous_id_ == b.anonymous_id_ && a.name_ == b.name_;
|
||||
}
|
||||
|
||||
inline AttributeIDRef::operator bool() const
|
||||
{
|
||||
return this->is_named() || this->is_anonymous();
|
||||
}
|
||||
|
||||
inline uint64_t AttributeIDRef::hash() const
|
||||
{
|
||||
return get_default_hash_2(name_, anonymous_id_);
|
||||
}
|
||||
|
||||
inline bool AttributeIDRef::is_named() const
|
||||
{
|
||||
return !name_.is_empty();
|
||||
}
|
||||
|
||||
inline bool AttributeIDRef::is_anonymous() const
|
||||
{
|
||||
return anonymous_id_ != nullptr;
|
||||
}
|
||||
|
||||
inline StringRef AttributeIDRef::name() const
|
||||
{
|
||||
BLI_assert(this->is_named());
|
||||
return name_;
|
||||
}
|
||||
|
||||
inline const AnonymousAttributeID &AttributeIDRef::anonymous_id() const
|
||||
{
|
||||
BLI_assert(this->is_anonymous());
|
||||
return *anonymous_id_;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* #OutputAttribute inline methods.
|
||||
*/
|
||||
|
||||
inline OutputAttribute::OutputAttribute() = default;
|
||||
inline OutputAttribute::OutputAttribute(OutputAttribute &&other) = default;
|
||||
|
||||
inline OutputAttribute::OutputAttribute(GVMutableArrayPtr varray,
|
||||
AttributeDomain domain,
|
||||
SaveFn save,
|
||||
const bool ignore_old_values)
|
||||
: varray_(std::move(varray)),
|
||||
domain_(domain),
|
||||
save_(std::move(save)),
|
||||
ignore_old_values_(ignore_old_values)
|
||||
{
|
||||
}
|
||||
|
||||
inline OutputAttribute::operator bool() const
|
||||
{
|
||||
return varray_.get() != nullptr;
|
||||
}
|
||||
|
||||
inline GVMutableArray &OutputAttribute::operator*()
|
||||
{
|
||||
return *varray_;
|
||||
}
|
||||
|
||||
inline GVMutableArray *OutputAttribute::operator->()
|
||||
{
|
||||
return varray_.get();
|
||||
}
|
||||
|
||||
inline GVMutableArray &OutputAttribute::varray()
|
||||
{
|
||||
return *varray_;
|
||||
}
|
||||
|
||||
inline AttributeDomain OutputAttribute::domain() const
|
||||
{
|
||||
return domain_;
|
||||
}
|
||||
|
||||
inline const CPPType &OutputAttribute::cpp_type() const
|
||||
{
|
||||
return varray_->type();
|
||||
}
|
||||
|
||||
inline CustomDataType OutputAttribute::custom_data_type() const
|
||||
{
|
||||
return cpp_type_to_custom_data_type(this->cpp_type());
|
||||
}
|
||||
|
||||
template<typename T> inline MutableSpan<T> OutputAttribute::as_span()
|
||||
{
|
||||
return this->as_span().typed<T>();
|
||||
}
|
||||
|
||||
} // namespace blender::bke
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* Extern template instantiations that are defined in `intern/extern_implementations.cc`.
|
||||
*/
|
||||
|
||||
namespace blender::bke {
|
||||
extern template class OutputAttribute_Typed<float>;
|
||||
extern template class OutputAttribute_Typed<int>;
|
||||
extern template class OutputAttribute_Typed<float3>;
|
||||
extern template class OutputAttribute_Typed<bool>;
|
||||
extern template class OutputAttribute_Typed<ColorGeometry4f>;
|
||||
} // namespace blender::bke
|
||||
|
@@ -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 */
|
||||
|
@@ -70,10 +70,7 @@ void BKE_curveprofile_reset_view(struct CurveProfile *profile);
|
||||
|
||||
void BKE_curveprofile_reset(struct CurveProfile *profile);
|
||||
|
||||
void BKE_curveprofile_create_samples(struct CurveProfile *profile,
|
||||
int n_segments,
|
||||
bool sample_straight_edges,
|
||||
struct CurveProfilePoint *r_samples);
|
||||
int BKE_curveprofile_table_size(const struct CurveProfile *profile);
|
||||
|
||||
void BKE_curveprofile_init(struct CurveProfile *profile, short segments_len);
|
||||
|
||||
@@ -85,13 +82,6 @@ enum {
|
||||
};
|
||||
void BKE_curveprofile_update(struct CurveProfile *profile, const int update_flags);
|
||||
|
||||
/* Need to find the total length of the curve to sample a portion of it */
|
||||
float BKE_curveprofile_total_length(const struct CurveProfile *profile);
|
||||
|
||||
void BKE_curveprofile_create_samples_even_spacing(struct CurveProfile *profile,
|
||||
int n_segments,
|
||||
struct CurveProfilePoint *r_samples);
|
||||
|
||||
/* Length portion is the fraction of the total path length where we want the location */
|
||||
void BKE_curveprofile_evaluate_length_portion(const struct CurveProfile *profile,
|
||||
float length_portion,
|
||||
|
@@ -253,6 +253,13 @@ struct GeometrySet {
|
||||
blender::Map<GeometryComponentType, GeometryComponentPtr> components_;
|
||||
|
||||
public:
|
||||
GeometrySet();
|
||||
GeometrySet(const GeometrySet &other);
|
||||
GeometrySet(GeometrySet &&other);
|
||||
~GeometrySet();
|
||||
GeometrySet &operator=(const GeometrySet &other);
|
||||
GeometrySet &operator=(GeometrySet &&other);
|
||||
|
||||
GeometryComponent &get_component_for_write(GeometryComponentType component_type);
|
||||
template<typename Component> Component &get_component_for_write()
|
||||
{
|
||||
|
@@ -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,12 @@ 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
|
||||
#define GEO_NODE_POINTS_TO_VOLUME 1101
|
||||
#define GEO_NODE_CURVE_HANDLE_TYPE_SELECTION 1102
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
@@ -1525,6 +1532,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
|
||||
@@ -119,7 +120,7 @@ set(SRC
|
||||
intern/curve_deform.c
|
||||
intern/curve_eval.cc
|
||||
intern/curve_to_mesh_convert.cc
|
||||
intern/curveprofile.c
|
||||
intern/curveprofile.cc
|
||||
intern/customdata.c
|
||||
intern/customdata_file.c
|
||||
intern/data_transfer.c
|
||||
@@ -133,6 +134,7 @@ set(SRC
|
||||
intern/editmesh_cache.c
|
||||
intern/editmesh_tangent.c
|
||||
intern/effect.c
|
||||
intern/extern_implementations.cc
|
||||
intern/fcurve.c
|
||||
intern/fcurve_cache.c
|
||||
intern/fcurve_driver.c
|
||||
@@ -306,6 +308,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 +792,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,15 +380,55 @@ 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,
|
||||
CatalogID catalog_id,
|
||||
StringRef simple_name,
|
||||
const AssetCatalogTreeItem *parent)
|
||||
: name_(name), catalog_id_(catalog_id), parent_(parent)
|
||||
: name_(name), catalog_id_(catalog_id), simple_name_(simple_name), parent_(parent)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -375,16 +437,21 @@ CatalogID AssetCatalogTreeItem::get_catalog_id() const
|
||||
return catalog_id_;
|
||||
}
|
||||
|
||||
StringRef AssetCatalogTreeItem::get_name() const
|
||||
StringRefNull AssetCatalogTreeItem::get_name() const
|
||||
{
|
||||
return name_;
|
||||
}
|
||||
|
||||
CatalogPath AssetCatalogTreeItem::catalog_path() const
|
||||
StringRefNull AssetCatalogTreeItem::get_simple_name() const
|
||||
{
|
||||
std::string current_path = name_;
|
||||
return simple_name_;
|
||||
}
|
||||
|
||||
AssetCatalogPath AssetCatalogTreeItem::catalog_path() const
|
||||
{
|
||||
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 +472,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 +479,31 @@ 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,
|
||||
is_last_component ? catalog.simple_name : "",
|
||||
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 +634,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 +643,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 +758,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 +786,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); /* NOLINT: bugprone-use-after-move */
|
||||
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
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user