1
1
This repository has been archived on 2023-10-09. You can view files and clone it, but cannot push or open issues or pull requests.
Files
blender-archive/source/blender/blenkernel/intern/pbvh_pixels.cc

491 lines
15 KiB
C++

/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2022 Blender Foundation. All rights reserved. */
#include "BKE_customdata.h"
#include "BKE_mesh.h"
#include "BKE_mesh_mapping.h"
#include "BKE_pbvh.h"
#include "BKE_pbvh_pixels.hh"
#include "DNA_image_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_object_types.h"
#include "BLI_math.h"
#include "BLI_task.h"
#include "PIL_time_utildefines.h"
#include "BKE_image_wrappers.hh"
#include "bmesh.h"
#include "pbvh_intern.h"
namespace blender::bke::pbvh::pixels {
void Triangles::clear()
{
paint_input.clear();
if (gpu_buffer) {
GPU_storagebuf_free(gpu_buffer);
gpu_buffer = nullptr;
}
}
void Triangles::ensure_gpu_buffer()
{
if (gpu_buffer) {
return;
}
gpu_buffer = GPU_storagebuf_create_ex(
mem_size(), paint_input.data(), GPU_USAGE_STATIC, __func__);
}
/**
* Update the gpu buffer offsets of the given tiles.
* \return the total needed buffer length.
*/
static int64_t update_gpu_buffer_offsets(MutableSpan<UDIMTilePixels> tiles)
{
int64_t elem_len = 0;
for (UDIMTilePixels &tile : tiles) {
tile.gpu_buffer_offset = elem_len;
elem_len += tile.pixel_rows.size();
}
return elem_len;
}
static void flatten_pixel_rows(Vector<PackedPixelRow> &elements, Span<UDIMTilePixels> tiles)
{
for (const UDIMTilePixels &tile : tiles) {
BLI_assert(elements.size() == tile.gpu_buffer_offset);
elements.extend(tile.pixel_rows);
}
}
void NodeData::build_pixels_gpu_buffer()
{
BLI_assert(gpu_buffers.pixels == nullptr);
int64_t elem_len = update_gpu_buffer_offsets(tiles);
/* TODO(jbakker): we should store the packed pixels in a single vector per node to reduce
* copying. */
Vector<PackedPixelRow> elements;
elements.reserve(elem_len);
flatten_pixel_rows(elements, tiles);
gpu_buffers.pixels = GPU_storagebuf_create_ex(
elem_len * sizeof(PackedPixelRow), elements.data(), GPU_USAGE_STATIC, __func__);
}
void UDIMTilePixels::init_gpu_sub_tiles()
{
BLI_assert(gpu_sub_tiles.is_empty());
const int max_sub_tiles = 16;
bool sub_tiles_hit[max_sub_tiles][max_sub_tiles];
for (int x = 0; x < max_sub_tiles; x++) {
for (int y = 0; y < max_sub_tiles; y++) {
sub_tiles_hit[x][y] = false;
}
}
int2 max_sub_tile_len(0, 0);
for (const PackedPixelRow &elements : pixel_rows) {
int2 subtile_from = int2(elements.start_image_coordinate / TEXTURE_STREAMING_TILE_SIZE);
int2 coord_to = int2(elements.start_image_coordinate) + int2(elements.num_pixels + 1, 1);
int2 subtile_to = int2(coord_to / TEXTURE_STREAMING_TILE_SIZE);
for (int x = subtile_from.x; x < subtile_to.x; x++) {
sub_tiles_hit[x][subtile_from.y] = true;
}
}
for (int x = 0; x < max_sub_tiles; x++) {
for (int y = 0; y < max_sub_tiles; y++) {
if (sub_tiles_hit[x][y]) {
gpu_sub_tiles.append(int2(x, y));
}
}
}
}
void NodeData::init_gpu_sub_tiles()
{
printf("%s\n", __func__);
for (UDIMTilePixels &tile : tiles) {
tile.init_gpu_sub_tiles();
}
}
/**
* During debugging this check could be enabled.
* It will write to each image pixel that is covered by the PBVH.
*/
constexpr bool USE_WATERTIGHT_CHECK = false;
/**
* Calculate the delta of two neighbor UV coordinates in the given image buffer.
*/
static float2 calc_barycentric_delta(const float2 uvs[3],
const float2 start_uv,
const float2 end_uv)
{
float3 start_barycentric;
barycentric_weights_v2(uvs[0], uvs[1], uvs[2], start_uv, start_barycentric);
float3 end_barycentric;
barycentric_weights_v2(uvs[0], uvs[1], uvs[2], end_uv, end_barycentric);
float3 barycentric = end_barycentric - start_barycentric;
return float2(barycentric.x, barycentric.y);
}
static float2 calc_barycentric_delta_x(const ImBuf *image_buffer,
const float2 uvs[3],
const int x,
const int y)
{
const float2 start_uv(float(x) / image_buffer->x, float(y) / image_buffer->y);
const float2 end_uv(float(x + 1) / image_buffer->x, float(y) / image_buffer->y);
return calc_barycentric_delta(uvs, start_uv, end_uv);
}
static void extract_barycentric_pixels(UDIMTilePixels &tile_data,
const ImBuf *image_buffer,
const int triangle_index,
const float2 uvs[3],
const int minx,
const int miny,
const int maxx,
const int maxy)
{
for (int y = miny; y < maxy; y++) {
bool start_detected = false;
PackedPixelRow pixel_row;
pixel_row.triangle_index = triangle_index;
pixel_row.num_pixels = 0;
int x;
for (x = minx; x < maxx; x++) {
float2 uv((float(x) + 0.5f) / image_buffer->x, (float(y) + 0.5f) / image_buffer->y);
float3 barycentric_weights;
barycentric_weights_v2(uvs[0], uvs[1], uvs[2], uv, barycentric_weights);
const bool is_inside = barycentric_inside_triangle_v2(barycentric_weights);
if (!start_detected && is_inside) {
start_detected = true;
pixel_row.start_image_coordinate = ushort2(x, y);
pixel_row.start_barycentric_coord = float2(barycentric_weights.x, barycentric_weights.y);
}
else if (start_detected && !is_inside) {
break;
}
}
if (!start_detected) {
continue;
}
pixel_row.num_pixels = x - pixel_row.start_image_coordinate.x;
tile_data.pixel_rows.append(pixel_row);
}
}
static void init_triangles(PBVH *pbvh, PBVHNode *node, NodeData *node_data, const MLoop *mloop)
{
for (int i = 0; i < node->totprim; i++) {
const MLoopTri *lt = &pbvh->looptri[node->prim_indices[i]];
node_data->triangles.append(
int3(mloop[lt->tri[0]].v, mloop[lt->tri[1]].v, mloop[lt->tri[2]].v));
}
}
struct EncodePixelsUserData {
Image *image;
ImageUser *image_user;
PBVH *pbvh;
Vector<PBVHNode *> *nodes;
const MLoopUV *ldata_uv;
};
static void do_encode_pixels(void *__restrict userdata,
const int n,
const TaskParallelTLS *__restrict UNUSED(tls))
{
EncodePixelsUserData *data = static_cast<EncodePixelsUserData *>(userdata);
Image *image = data->image;
ImageUser image_user = *data->image_user;
PBVH *pbvh = data->pbvh;
PBVHNode *node = (*data->nodes)[n];
NodeData *node_data = static_cast<NodeData *>(node->pixels.node_data);
LISTBASE_FOREACH (ImageTile *, tile, &data->image->tiles) {
image::ImageTileWrapper image_tile(tile);
image_user.tile = image_tile.get_tile_number();
ImBuf *image_buffer = BKE_image_acquire_ibuf(image, &image_user, nullptr);
if (image_buffer == nullptr) {
continue;
}
float2 tile_offset = float2(image_tile.get_tile_offset());
UDIMTilePixels tile_data;
Triangles &triangles = node_data->triangles;
for (int triangle_index = 0; triangle_index < triangles.size(); triangle_index++) {
const MLoopTri *lt = &pbvh->looptri[node->prim_indices[triangle_index]];
float2 uvs[3] = {
float2(data->ldata_uv[lt->tri[0]].uv) - tile_offset,
float2(data->ldata_uv[lt->tri[1]].uv) - tile_offset,
float2(data->ldata_uv[lt->tri[2]].uv) - tile_offset,
};
const float minv = clamp_f(min_fff(uvs[0].y, uvs[1].y, uvs[2].y), 0.0f, 1.0f);
const int miny = floor(minv * image_buffer->y);
const float maxv = clamp_f(max_fff(uvs[0].y, uvs[1].y, uvs[2].y), 0.0f, 1.0f);
const int maxy = min_ii(ceil(maxv * image_buffer->y), image_buffer->y);
const float minu = clamp_f(min_fff(uvs[0].x, uvs[1].x, uvs[2].x), 0.0f, 1.0f);
const int minx = floor(minu * image_buffer->x);
const float maxu = clamp_f(max_fff(uvs[0].x, uvs[1].x, uvs[2].x), 0.0f, 1.0f);
const int maxx = min_ii(ceil(maxu * image_buffer->x), image_buffer->x);
TrianglePaintInput &triangle = triangles.get_paint_input(triangle_index);
triangle.delta_barycentric_coord = calc_barycentric_delta_x(image_buffer, uvs, minx, miny);
extract_barycentric_pixels(
tile_data, image_buffer, triangle_index, uvs, minx, miny, maxx, maxy);
}
BKE_image_release_ibuf(image, image_buffer, nullptr);
if (tile_data.pixel_rows.is_empty()) {
continue;
}
tile_data.tile_number = image_tile.get_tile_number();
node_data->tiles.append(tile_data);
}
}
static bool should_pixels_be_updated(PBVHNode *node)
{
if ((node->flag & PBVH_Leaf) == 0) {
return false;
}
if ((node->flag & PBVH_RebuildPixels) != 0) {
return true;
}
NodeData *node_data = static_cast<NodeData *>(node->pixels.node_data);
if (node_data != nullptr) {
return false;
}
return true;
}
static int64_t count_nodes_to_update(PBVH *pbvh)
{
int64_t result = 0;
for (int n = 0; n < pbvh->totnode; n++) {
PBVHNode *node = &pbvh->nodes[n];
if (should_pixels_be_updated(node)) {
result++;
}
}
return result;
}
/**
* Find the nodes that needs to be updated.
*
* The nodes that require updated are added to the r_nodes_to_update parameter.
* Will fill in r_visited_polygons with polygons that are owned by nodes that do not require
* updates.
*
* returns if there were any nodes found (true).
*/
static bool find_nodes_to_update(PBVH *pbvh, Vector<PBVHNode *> &r_nodes_to_update)
{
int64_t nodes_to_update_len = count_nodes_to_update(pbvh);
if (nodes_to_update_len == 0) {
return false;
}
r_nodes_to_update.reserve(nodes_to_update_len);
for (int n = 0; n < pbvh->totnode; n++) {
PBVHNode *node = &pbvh->nodes[n];
if (!should_pixels_be_updated(node)) {
continue;
}
r_nodes_to_update.append(node);
node->flag = static_cast<PBVHNodeFlags>(node->flag | PBVH_RebuildPixels);
if (node->pixels.node_data == nullptr) {
NodeData *node_data = MEM_new<NodeData>(__func__);
node->pixels.node_data = node_data;
}
else {
NodeData *node_data = static_cast<NodeData *>(node->pixels.node_data);
node_data->clear_data();
}
}
return true;
}
static void apply_watertight_check(PBVH *pbvh, Image *image, ImageUser *image_user)
{
ImageUser watertight = *image_user;
LISTBASE_FOREACH (ImageTile *, tile_data, &image->tiles) {
image::ImageTileWrapper image_tile(tile_data);
watertight.tile = image_tile.get_tile_number();
ImBuf *image_buffer = BKE_image_acquire_ibuf(image, &watertight, nullptr);
if (image_buffer == nullptr) {
continue;
}
for (int n = 0; n < pbvh->totnode; n++) {
PBVHNode *node = &pbvh->nodes[n];
if ((node->flag & PBVH_Leaf) == 0) {
continue;
}
NodeData *node_data = static_cast<NodeData *>(node->pixels.node_data);
UDIMTilePixels *tile_node_data = node_data->find_tile_data(image_tile);
if (tile_node_data == nullptr) {
continue;
}
for (PackedPixelRow &pixel_row : tile_node_data->pixel_rows) {
int pixel_offset = pixel_row.start_image_coordinate.y * image_buffer->x +
pixel_row.start_image_coordinate.x;
for (int x = 0; x < pixel_row.num_pixels; x++) {
if (image_buffer->rect_float) {
copy_v4_fl(&image_buffer->rect_float[pixel_offset * 4], 1.0);
}
if (image_buffer->rect) {
uint8_t *dest = static_cast<uint8_t *>(
static_cast<void *>(&image_buffer->rect[pixel_offset]));
copy_v4_uchar(dest, 255);
}
pixel_offset += 1;
}
}
}
BKE_image_release_ibuf(image, image_buffer, nullptr);
}
BKE_image_partial_update_mark_full_update(image);
}
static void update_pixels(PBVH *pbvh, Mesh *mesh, Image *image, ImageUser *image_user)
{
Vector<PBVHNode *> nodes_to_update;
if (!find_nodes_to_update(pbvh, nodes_to_update)) {
return;
}
const MLoopUV *ldata_uv = static_cast<const MLoopUV *>(
CustomData_get_layer(&mesh->ldata, CD_MLOOPUV));
if (ldata_uv == nullptr) {
return;
}
for (PBVHNode *node : nodes_to_update) {
NodeData *node_data = static_cast<NodeData *>(node->pixels.node_data);
const Span<MLoop> loops = mesh->loops();
init_triangles(pbvh, node, node_data, loops.data());
}
EncodePixelsUserData user_data;
user_data.pbvh = pbvh;
user_data.image = image;
user_data.image_user = image_user;
user_data.ldata_uv = ldata_uv;
user_data.nodes = &nodes_to_update;
TaskParallelSettings settings;
BKE_pbvh_parallel_range_settings(&settings, true, nodes_to_update.size());
BLI_task_parallel_range(0, nodes_to_update.size(), &user_data, do_encode_pixels, &settings);
if (USE_WATERTIGHT_CHECK) {
apply_watertight_check(pbvh, image, image_user);
}
/* Rebuild the undo regions. */
for (PBVHNode *node : nodes_to_update) {
NodeData *node_data = static_cast<NodeData *>(node->pixels.node_data);
node_data->rebuild_undo_regions();
}
/* Clear the UpdatePixels flag. */
for (PBVHNode *node : nodes_to_update) {
node->flag = static_cast<PBVHNodeFlags>(node->flag & ~PBVH_RebuildPixels);
}
//#define DO_PRINT_STATISTICS
#ifdef DO_PRINT_STATISTICS
/* Print some statistics about compression ratio. */
{
int64_t compressed_data_len = 0;
int64_t num_pixels = 0;
for (int n = 0; n < pbvh->totnode; n++) {
PBVHNode *node = &pbvh->nodes[n];
if ((node->flag & PBVH_Leaf) == 0) {
continue;
}
NodeData *node_data = static_cast<NodeData *>(node->pixels.node_data);
compressed_data_len += node_data->triangles.mem_size();
for (const UDIMTilePixels &tile_data : node_data->tiles) {
compressed_data_len += tile_data.encoded_pixels.size() * sizeof(PackedPixelRow);
for (const PackedPixelRow &encoded_pixels : tile_data.encoded_pixels) {
num_pixels += encoded_pixels.num_pixels;
}
}
}
printf("Encoded %lld pixels in %lld bytes (%f bytes per pixel)\n",
num_pixels,
compressed_data_len,
float(compressed_data_len) / num_pixels);
}
#endif
}
NodeData &BKE_pbvh_pixels_node_data_get(PBVHNode &node)
{
BLI_assert(node.pixels.node_data != nullptr);
NodeData *node_data = static_cast<NodeData *>(node.pixels.node_data);
return *node_data;
}
void BKE_pbvh_pixels_mark_image_dirty(PBVHNode &node, Image &image, ImageUser &image_user)
{
BLI_assert(node.pixels.node_data != nullptr);
NodeData *node_data = static_cast<NodeData *>(node.pixels.node_data);
if (node_data->flags.dirty) {
ImageUser local_image_user = image_user;
LISTBASE_FOREACH (ImageTile *, tile, &image.tiles) {
image::ImageTileWrapper image_tile(tile);
local_image_user.tile = image_tile.get_tile_number();
ImBuf *image_buffer = BKE_image_acquire_ibuf(&image, &local_image_user, nullptr);
if (image_buffer == nullptr) {
continue;
}
node_data->mark_region(image, image_tile, *image_buffer);
BKE_image_release_ibuf(&image, image_buffer, nullptr);
}
node_data->flags.dirty = false;
}
}
} // namespace blender::bke::pbvh::pixels
extern "C" {
using namespace blender::bke::pbvh::pixels;
void BKE_pbvh_build_pixels(PBVH *pbvh, Mesh *mesh, Image *image, ImageUser *image_user)
{
update_pixels(pbvh, mesh, image, image_user);
}
void pbvh_pixels_free(PBVHNode *node)
{
NodeData *node_data = static_cast<NodeData *>(node->pixels.node_data);
MEM_delete(node_data);
node->pixels.node_data = nullptr;
}
}