Caching generated image leads to infinite loop in preview render #53

Merged
Bogdan Nagirniak merged 12 commits from BLEN-409 into hydra-render 2023-06-13 18:39:39 +02:00
15 changed files with 75 additions and 138 deletions
Showing only changes of commit f167b27a6c - Show all commits

View File

@ -38,7 +38,6 @@ __all__ = (
"HydraRenderEngine", "HydraRenderEngine",
"export_mtlx", "export_mtlx",
"register_plugins", "register_plugins",
"get_render_plugins",
) )
import os import os
@ -48,7 +47,7 @@ from pathlib import Path
import bpy import bpy
import _bpy_hydra import _bpy_hydra
from _bpy_hydra import register_plugins, get_render_plugins from _bpy_hydra import register_plugins
class HydraRenderEngine(bpy.types.RenderEngine): class HydraRenderEngine(bpy.types.RenderEngine):
@ -68,7 +67,6 @@ class HydraRenderEngine(bpy.types.RenderEngine):
@classmethod @classmethod
def register(cls): def register(cls):
_bpy_hydra.init()
root_folder = "blender.shared" if platform.system() == 'Windows' else "lib" root_folder = "blender.shared" if platform.system() == 'Windows' else "lib"
os.environ['PXR_MTLX_STDLIB_SEARCH_PATHS'] = os.pathsep.join([ os.environ['PXR_MTLX_STDLIB_SEARCH_PATHS'] = os.pathsep.join([
str(Path(bpy.app.binary_path).parent / f"{root_folder}/materialx/libraries"), str(Path(bpy.app.binary_path).parent / f"{root_folder}/materialx/libraries"),

View File

@ -21,7 +21,6 @@ void FinalEngine::render(Depsgraph *depsgraph)
{ {
/* Release the GIL before calling into hydra, in case any hydra plugins call into python. */ /* Release the GIL before calling into hydra, in case any hydra plugins call into python. */
pxr::TF_PY_ALLOW_THREADS_IN_SCOPE();
engine_->Execute(render_index_.get(), &tasks_); engine_->Execute(render_index_.get(), &tasks_);
} }
@ -143,7 +142,6 @@ void FinalEngineGL::render(Depsgraph *depsgraph)
{ {
/* Release the GIL before calling into hydra, in case any hydra plugins call into python. */ /* Release the GIL before calling into hydra, in case any hydra plugins call into python. */
pxr::TF_PY_ALLOW_THREADS_IN_SCOPE();
engine_->Execute(render_index_.get(), &tasks_); engine_->Execute(render_index_.get(), &tasks_);
} }

View File

@ -11,7 +11,6 @@ void PreviewEngine::render(Depsgraph *depsgraph)
{ {
/* Release the GIL before calling into hydra, in case any hydra plugins call into python. */ /* Release the GIL before calling into hydra, in case any hydra plugins call into python. */
pxr::TF_PY_ALLOW_THREADS_IN_SCOPE();
engine_->Execute(render_index_.get(), &tasks_); engine_->Execute(render_index_.get(), &tasks_);
} }

View File

@ -21,16 +21,6 @@
namespace blender::render::hydra { namespace blender::render::hydra {
static PyObject *init_func(PyObject * /*self*/, PyObject *args)
{
CLOG_INFO(LOG_RENDER_HYDRA, 0, "Init");
pxr::PlugRegistry::GetInstance().RegisterPlugins(std::string(BKE_appdir_program_dir()) +
"/blender.shared/usd");
Py_RETURN_NONE;
}
static PyObject *register_plugins_func(PyObject * /*self*/, PyObject *args) static PyObject *register_plugins_func(PyObject * /*self*/, PyObject *args)
{ {
PyObject *pyplugin_dirs; PyObject *pyplugin_dirs;
@ -65,41 +55,6 @@ static PyObject *register_plugins_func(PyObject * /*self*/, PyObject *args)
Py_RETURN_NONE; Py_RETURN_NONE;
} }
static PyObject *get_render_plugins_func(PyObject * /*self*/, PyObject *args)
{
pxr::PlugRegistry &registry = pxr::PlugRegistry::GetInstance();
pxr::TfTokenVector plugin_ids = pxr::UsdImagingGLEngine::GetRendererPlugins();
PyObject *ret = PyTuple_New(plugin_ids.size());
PyObject *val;
for (int i = 0; i < plugin_ids.size(); ++i) {
PyObject *descr = PyDict_New();
PyDict_SetItemString(descr, "id", val = PyUnicode_FromString(plugin_ids[i].GetText()));
Py_DECREF(val);
PyDict_SetItemString(
descr,
"name",
val = PyUnicode_FromString(
pxr::UsdImagingGLEngine::GetRendererDisplayName(plugin_ids[i]).c_str()));
Py_DECREF(val);
std::string plugin_name = plugin_ids[i];
plugin_name = plugin_name.substr(0, plugin_name.size() - 6);
plugin_name[0] = tolower(plugin_name[0]);
std::string path = "";
pxr::PlugPluginPtr plugin = registry.GetPluginWithName(plugin_name);
if (plugin) {
path = plugin->GetPath();
}
PyDict_SetItemString(descr, "path", val = PyUnicode_FromString(path.c_str()));
Py_DECREF(val);
PyTuple_SetItem(ret, i, descr);
}
return ret;
}
static PyObject *engine_create_func(PyObject * /*self*/, PyObject *args) static PyObject *engine_create_func(PyObject * /*self*/, PyObject *args)
{ {
PyObject *pyengine; PyObject *pyengine;
@ -283,9 +238,7 @@ static PyObject *cache_image_func(PyObject * /*self*/, PyObject *args)
} }
static PyMethodDef methods[] = { static PyMethodDef methods[] = {
{"init", init_func, METH_VARARGS, ""},
{"register_plugins", register_plugins_func, METH_VARARGS, ""}, {"register_plugins", register_plugins_func, METH_VARARGS, ""},
{"get_render_plugins", get_render_plugins_func, METH_VARARGS, ""},
{"engine_create", engine_create_func, METH_VARARGS, ""}, {"engine_create", engine_create_func, METH_VARARGS, ""},
{"engine_free", engine_free_func, METH_VARARGS, ""}, {"engine_free", engine_free_func, METH_VARARGS, ""},

View File

@ -4,6 +4,7 @@
#include <bitset> #include <bitset>
#include "BKE_object.h" #include "BKE_object.h"
#include "BLI_set.hh"
#include "DEG_depsgraph_query.h" #include "DEG_depsgraph_query.h"
#include "DNA_scene_types.h" #include "DNA_scene_types.h"
@ -236,14 +237,17 @@ void BlenderSceneDelegate::set_setting(const std::string &key, const pxr::VtValu
if (key == "MaterialXFilenameKey") { if (key == "MaterialXFilenameKey") {
settings.mx_filename_key = pxr::TfToken(val.Get<std::string>()); settings.mx_filename_key = pxr::TfToken(val.Get<std::string>());
} }
else {
settings.render_tokens.add_overwrite(pxr::TfToken(key), val);
}
} }
pxr::SdfPath BlenderSceneDelegate::prim_id(ID *id, const char *prefix) const pxr::SdfPath BlenderSceneDelegate::prim_id(ID *id, const char *prefix) const
{ {
/* Making id of object in form like <prefix>_<pointer in 16 hex digits format> */ /* Making id of object in form like <prefix>_<pointer in 16 hex digits format> */
char str[32]; char name[32];
snprintf(str, 32, "%s_%016llx", prefix, (uintptr_t)id); snprintf(name, sizeof(name), "%s_%016llx", prefix, (uintptr_t)id);
return GetDelegateID().AppendElementString(str); return GetDelegateID().AppendElementString(name);
} }
pxr::SdfPath BlenderSceneDelegate::object_prim_id(Object *object) const pxr::SdfPath BlenderSceneDelegate::object_prim_id(Object *object) const

View File

@ -11,7 +11,7 @@
#include "CLG_log.h" #include "CLG_log.h"
#include "BLI_set.hh" #include "BLI_map.hh"
#include "curves.h" #include "curves.h"
#include "instancer.h" #include "instancer.h"
#include "light.h" #include "light.h"
@ -34,6 +34,7 @@ class BlenderSceneDelegate : public pxr::HdSceneDelegate {
public: public:
struct Settings { struct Settings {
pxr::TfToken mx_filename_key; pxr::TfToken mx_filename_key;
Map<pxr::TfToken, pxr::VtValue> render_tokens;
}; };
BlenderSceneDelegate(pxr::HdRenderIndex *parent_index, BlenderSceneDelegate(pxr::HdRenderIndex *parent_index,

View File

@ -69,17 +69,16 @@ void CurvesData::update()
pxr::VtValue CurvesData::get_data(pxr::SdfPath const &id, pxr::TfToken const &key) const pxr::VtValue CurvesData::get_data(pxr::SdfPath const &id, pxr::TfToken const &key) const
{ {
pxr::VtValue ret;
if (key == pxr::HdTokens->points) { if (key == pxr::HdTokens->points) {
ret = vertices_; return pxr::VtValue(vertices_);
} }
else if (key == pxr::HdPrimvarRoleTokens->textureCoordinate) { else if (key == pxr::HdPrimvarRoleTokens->textureCoordinate) {
ret = uvs_; return pxr::VtValue(uvs_);
} }
else if (key == pxr::HdTokens->widths) { else if (key == pxr::HdTokens->widths) {
ret = widths_; return pxr::VtValue(widths_);
} }
return ret; return pxr::VtValue();
} }
bool CurvesData::update_visibility() bool CurvesData::update_visibility()
@ -110,19 +109,17 @@ pxr::HdPrimvarDescriptorVector CurvesData::primvar_descriptors(
if (!vertices_.empty()) { if (!vertices_.empty()) {
primvars.emplace_back(pxr::HdTokens->points, interpolation, pxr::HdPrimvarRoleTokens->point); primvars.emplace_back(pxr::HdTokens->points, interpolation, pxr::HdPrimvarRoleTokens->point);
} }
if (!widths_.empty()) {
primvars.emplace_back(pxr::HdTokens->widths, interpolation, pxr::HdPrimvarRoleTokens->none);
}
} }
else if (interpolation == pxr::HdInterpolationFaceVarying) { else if (interpolation == pxr::HdInterpolationConstant) {
if (!uvs_.empty()) { if (!uvs_.empty()) {
primvars.emplace_back(pxr::HdPrimvarRoleTokens->textureCoordinate, primvars.emplace_back(pxr::HdPrimvarRoleTokens->textureCoordinate,
interpolation, interpolation,
pxr::HdPrimvarRoleTokens->textureCoordinate); pxr::HdPrimvarRoleTokens->textureCoordinate);
} }
} }
else if (interpolation == pxr::HdInterpolationConstant) {
if (!widths_.empty()) {
primvars.emplace_back(pxr::HdTokens->widths, interpolation, pxr::HdPrimvarRoleTokens->none);
}
}
return primvars; return primvars;
} }
@ -160,14 +157,12 @@ void CurvesData::write_curves(Curves *curves)
curve_vertex_counts_.push_back(num_points); curve_vertex_counts_.push_back(num_points);
/* Set radius similar to Cycles if isn't set */ /* Set radius similar to Cycles if isn't set */
widths_.push_back(radii ? radii[i] : 0.01f);
for (int j = 0; j < num_points; j++) { for (int j = 0; j < num_points; j++) {
int ind = first_point_index + j; int ind = first_point_index + j;
widths_.push_back(radii ? radii[ind] * 2 : 0.01f);
vertices_.push_back(pxr::GfVec3f(positions[ind][0], positions[ind][1], positions[ind][2])); vertices_.push_back(pxr::GfVec3f(positions[ind][0], positions[ind][1], positions[ind][2]));
} }
} }
write_uv_maps(curves); write_uv_maps(curves);
} }
@ -188,8 +183,9 @@ void CurvesData::write_material()
{ {
Object *object = (Object *)id; Object *object = (Object *)id;
Material *mat = nullptr; Material *mat = nullptr;
/* TODO: Using only first material. Add support for multimaterial. */
if (BKE_object_material_count_eval(object) > 0) { if (BKE_object_material_count_eval(object) > 0) {
mat = BKE_object_material_get_eval(object, object->actcol); mat = BKE_object_material_get_eval(object, 0);
} }
if (!mat) { if (!mat) {

View File

@ -17,6 +17,13 @@ template<> struct blender::DefaultHash<pxr::SdfPath> {
} }
}; };
template<> struct blender::DefaultHash<pxr::TfToken> {
uint64_t operator()(const pxr::TfToken &value) const
{
return pxr::TfHash()(value);
}
};
namespace blender::render::hydra { namespace blender::render::hydra {
class BlenderSceneDelegate; class BlenderSceneDelegate;

View File

@ -20,7 +20,8 @@ namespace blender::render::hydra {
std::string cache_image_file( std::string cache_image_file(
Image *image, bContext *context, Scene *scene, ImageUser *iuser, bool check_exist) Image *image, bContext *context, Scene *scene, ImageUser *iuser, bool check_exist)
{ {
std::string file_path(FILE_MAX, 0); char file_path[FILE_MAX];
char file_name[32];
Main *main = CTX_data_main(context); Main *main = CTX_data_main(context);
ImageSaveOptions opts; ImageSaveOptions opts;
if (BKE_image_save_options_init(&opts, main, scene, image, iuser, false, false)) { if (BKE_image_save_options_init(&opts, main, scene, image, iuser, false, false)) {
@ -31,22 +32,22 @@ std::string cache_image_file(
int len = strlen(file_name); int len = strlen(file_name);
STR_CONCAT(file_name, len, *r_ext); STR_CONCAT(file_name, len, *r_ext);
BLI_path_join(file_path.data(), BLI_path_join(file_path,
file_path.capacity(), sizeof(file_path),
BKE_tempdir_session(), BKE_tempdir_session(),
"hydra_image_cache", "hydra_image_cache",
file_name); file_name);
if (check_exist && BLI_exists(file_path.c_str())) { if (check_exist && BLI_exists(file_path)) {
return file_path; return file_path;
} }
opts.save_copy = true; opts.save_copy = true;
STRNCPY(opts.filepath, file_path.c_str()); STRNCPY(opts.filepath, file_path);
if (BKE_image_save(nullptr, main, image, iuser, &opts)) { if (BKE_image_save(nullptr, main, image, iuser, &opts)) {
CLOG_INFO(LOG_RENDER_HYDRA_SCENE, 1, "%s -> %s", image->id.name, file_path.c_str()); CLOG_INFO(LOG_RENDER_HYDRA_SCENE, 1, "%s -> %s", image->id.name, file_path);
} }
else { else {
file_path = ""; memset(file_path, 0, sizeof(file_path));
} }
} }
BKE_image_save_options_free(&opts); BKE_image_save_options_free(&opts);

View File

@ -102,11 +102,10 @@ void InstancerData::update()
pxr::VtValue InstancerData::get_data(pxr::TfToken const &key) const pxr::VtValue InstancerData::get_data(pxr::TfToken const &key) const
{ {
ID_LOG(3, "%s", key.GetText()); ID_LOG(3, "%s", key.GetText());
pxr::VtValue ret;
if (key == pxr::HdInstancerTokens->instanceTransform) { if (key == pxr::HdInstancerTokens->instanceTransform) {
ret = mesh_transforms_; return pxr::VtValue(mesh_transforms_);
} }
return ret; return pxr::VtValue();
} }
bool InstancerData::update_visibility() bool InstancerData::update_visibility()
@ -128,7 +127,7 @@ bool InstancerData::update_visibility()
char name[16]; char name[16];
for (auto &l_inst : light_instances_.values()) { for (auto &l_inst : light_instances_.values()) {
for (int i = 0; i < l_inst.count; ++i) { for (int i = 0; i < l_inst.count; ++i) {
snprintf(name, 16, "L_%08x", i); snprintf(name, sizeof(name), "L_%08x", i);
change_tracker.MarkRprimDirty(l_inst.data->prim_id.AppendElementString(name), change_tracker.MarkRprimDirty(l_inst.data->prim_id.AppendElementString(name),
pxr::HdChangeTracker::DirtyVisibility); pxr::HdChangeTracker::DirtyVisibility);
} }
@ -321,15 +320,15 @@ bool InstancerData::is_instance_visible(Object *object)
pxr::SdfPath InstancerData::object_prim_id(Object *object) const pxr::SdfPath InstancerData::object_prim_id(Object *object) const
{ {
/* Making id of object in form like <prefix>_<pointer in 16 hex digits format> */ /* Making id of object in form like <prefix>_<pointer in 16 hex digits format> */
char str[32]; char name[32];
snprintf(str, 32, "O_%016llx", (uint64_t)object); snprintf(name, sizeof(name), "O_%016llx", (uint64_t)object);
return prim_id.AppendElementString(str); return prim_id.AppendElementString(name);
} }
pxr::SdfPath InstancerData::light_prim_id(LightInstance const &inst, int index) const pxr::SdfPath InstancerData::light_prim_id(LightInstance const &inst, int index) const
{ {
char name[16]; char name[16];
snprintf(name, 16, "L_%08x", index); snprintf(name, sizeof(name), "L_%08x", index);
return inst.data->prim_id.AppendElementString(name); return inst.data->prim_id.AppendElementString(name);
} }

View File

@ -142,20 +142,15 @@ pxr::VtValue LightData::get_data(pxr::TfToken const &key) const
pxr::VtValue ret; pxr::VtValue ret;
auto it = data_.find(key); auto it = data_.find(key);
if (it != data_.end()) { if (it != data_.end()) {
ret = it->second; return pxr::VtValue(it->second);
} }
else {
std::string n = key.GetString(); pxr::VtValue *ret_ptr = scene_delegate_->settings.render_tokens.lookup_ptr(key);
if (boost::algorithm::contains(n, "object:visibility:")) { if (ret_ptr) {
if (boost::algorithm::ends_with(n, "camera") || boost::algorithm::ends_with(n, "shadow")) { return *ret_ptr;
ret = false;
}
else {
ret = true;
}
}
} }
return ret;
return pxr::VtValue();
} }
bool LightData::update_visibility() bool LightData::update_visibility()
@ -171,38 +166,32 @@ bool LightData::update_visibility()
pxr::TfToken LightData::prim_type(Light *light) pxr::TfToken LightData::prim_type(Light *light)
{ {
pxr::TfToken ret;
switch (light->type) { switch (light->type) {
case LA_LOCAL: case LA_LOCAL:
case LA_SPOT: case LA_SPOT:
ret = pxr::HdPrimTypeTokens->sphereLight; return pxr::TfToken(pxr::HdPrimTypeTokens->sphereLight);
break;
case LA_SUN: case LA_SUN:
ret = pxr::HdPrimTypeTokens->distantLight; return pxr::TfToken(pxr::HdPrimTypeTokens->distantLight);
break;
case LA_AREA: case LA_AREA:
switch (light->area_shape) { switch (light->area_shape) {
case LA_AREA_SQUARE: case LA_AREA_SQUARE:
case LA_AREA_RECT: case LA_AREA_RECT:
ret = pxr::HdPrimTypeTokens->rectLight; return pxr::TfToken(pxr::HdPrimTypeTokens->rectLight);
break;
case LA_AREA_DISK: case LA_AREA_DISK:
case LA_AREA_ELLIPSE: case LA_AREA_ELLIPSE:
ret = pxr::HdPrimTypeTokens->diskLight; return pxr::TfToken(pxr::HdPrimTypeTokens->diskLight);
break;
default: default:
ret = pxr::HdPrimTypeTokens->rectLight; return pxr::TfToken(pxr::HdPrimTypeTokens->rectLight);
} }
break; break;
default: default:
ret = pxr::HdPrimTypeTokens->sphereLight; return pxr::TfToken(pxr::HdPrimTypeTokens->sphereLight);
} }
return ret;
} }
} // namespace blender::render::hydra } // namespace blender::render::hydra

View File

@ -2,6 +2,7 @@
* Copyright 2011-2022 Blender Foundation */ * Copyright 2011-2022 Blender Foundation */
#include <Python.h> #include <Python.h>
#include <unicodeobject.h>
#include <pxr/imaging/hd/material.h> #include <pxr/imaging/hd/material.h>
#include <pxr/imaging/hd/renderDelegate.h> #include <pxr/imaging/hd/renderDelegate.h>
@ -74,14 +75,13 @@ void MaterialData::update()
pxr::VtValue MaterialData::get_data(pxr::TfToken const &key) const pxr::VtValue MaterialData::get_data(pxr::TfToken const &key) const
{ {
pxr::VtValue ret;
if (key == scene_delegate_->settings.mx_filename_key) { if (key == scene_delegate_->settings.mx_filename_key) {
if (!mtlx_path_.GetResolvedPath().empty()) {
ret = mtlx_path_;
}
ID_LOG(3, "%s", key.GetText()); ID_LOG(3, "%s", key.GetText());
if (!mtlx_path_.GetResolvedPath().empty()) {
return pxr::VtValue(mtlx_path_);
}
} }
return ret; return pxr::VtValue();
} }
pxr::VtValue MaterialData::get_material_resource() const pxr::VtValue MaterialData::get_material_resource() const
@ -112,7 +112,9 @@ void MaterialData::export_mtlx()
std::string path; std::string path;
if (!PyErr_Occurred()) { if (!PyErr_Occurred()) {
path = PyUnicode_AsUTF8(result); if (PyUnicode_Check(result)) {
path = PyUnicode_AsUTF8(result);
}
Py_DECREF(result); Py_DECREF(result);
} }
else { else {

View File

@ -26,18 +26,11 @@ void MeshData::init()
ID_LOG(1, ""); ID_LOG(1, "");
Object *object = (Object *)id; Object *object = (Object *)id;
if (object->type == OB_MESH && object->mode == OB_MODE_OBJECT && Mesh *mesh = BKE_object_to_mesh(nullptr, object, false);
BLI_listbase_is_empty(&object->modifiers)) if (mesh) {
{ write_submeshes(mesh);
write_submeshes((Mesh *)object->data);
}
else {
Mesh *mesh = BKE_object_to_mesh(nullptr, object, false);
if (mesh) {
write_submeshes(mesh);
}
BKE_object_to_mesh_clear(object);
} }
BKE_object_to_mesh_clear(object);
write_transform(); write_transform();
write_materials(); write_materials();
@ -86,17 +79,16 @@ void MeshData::update()
pxr::VtValue MeshData::get_data(pxr::SdfPath const &id, pxr::TfToken const &key) const pxr::VtValue MeshData::get_data(pxr::SdfPath const &id, pxr::TfToken const &key) const
{ {
pxr::VtValue ret;
if (key == pxr::HdTokens->points) { if (key == pxr::HdTokens->points) {
ret = vertices_; return pxr::VtValue(vertices_);
} }
else if (key == pxr::HdTokens->normals) { else if (key == pxr::HdTokens->normals) {
ret = submesh(id).normals; return pxr::VtValue(submesh(id).normals);
} }
else if (key == pxr::HdPrimvarRoleTokens->textureCoordinate) { else if (key == pxr::HdPrimvarRoleTokens->textureCoordinate) {
ret = submesh(id).uvs; return pxr::VtValue(submesh(id).uvs);
} }
return ret; return pxr::VtValue();
} }
bool MeshData::update_visibility() bool MeshData::update_visibility()
@ -194,7 +186,7 @@ pxr::SdfPathVector MeshData::submesh_paths() const
pxr::SdfPath MeshData::submesh_prim_id(int index) const pxr::SdfPath MeshData::submesh_prim_id(int index) const
{ {
char name[16]; char name[16];
snprintf(name, 16, "SM_%04x", index); snprintf(name, sizeof(name), "SM_%04x", index);
return prim_id.AppendElementString(name); return prim_id.AppendElementString(name);
} }

View File

@ -131,13 +131,12 @@ void WorldData::update(World *world)
pxr::VtValue WorldData::get_data(pxr::TfToken const &key) const pxr::VtValue WorldData::get_data(pxr::TfToken const &key) const
{ {
pxr::VtValue ret;
auto it = data_.find(key); auto it = data_.find(key);
if (it != data_.end()) { if (it != data_.end()) {
ret = it->second;
ID_LOG(3, "%s", key.GetText()); ID_LOG(3, "%s", key.GetText());
return pxr::VtValue(it->second);
} }
return ret; return pxr::VtValue();
} }
void WorldData::write_transform() void WorldData::write_transform()

View File

@ -255,7 +255,6 @@ void ViewportEngine::render(Depsgraph *depsgraph, bContext *context)
{ {
/* Release the GIL before calling into hydra, in case any hydra plugins call into python. */ /* Release the GIL before calling into hydra, in case any hydra plugins call into python. */
pxr::TF_PY_ALLOW_THREADS_IN_SCOPE();
engine_->Execute(render_index_.get(), &tasks); engine_->Execute(render_index_.get(), &tasks);
if ((bl_engine_->type->flag & RE_USE_GPU_CONTEXT) == 0) { if ((bl_engine_->type->flag & RE_USE_GPU_CONTEXT) == 0) {