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/render/intern/raytrace/reorganize.h

501 lines
12 KiB
C++

/*
* ***** BEGIN GPL LICENSE BLOCK *****
*
* 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) 2009 Blender Foundation.
* All rights reserved.
*
* The Original Code is: all of this file.
*
* Contributor(s): André Pinto.
*
* ***** END GPL LICENSE BLOCK *****
*/
/** \file blender/render/intern/raytrace/reorganize.h
* \ingroup render
*/
#include <float.h>
#include <math.h>
#include <stdio.h>
#include <algorithm>
#include <queue>
#include <vector>
#ifdef _WIN32
# ifdef INFINITY
# undef INFINITY
# endif
# define INFINITY FLT_MAX // in mingw math.h: (1.0F/0.0F). This generates compile error, though.
#endif
extern int tot_pushup;
extern int tot_pushdown;
#if !defined(INFINITY) && defined(HUGE_VAL)
#define INFINITY HUGE_VAL
#endif
template<class Node>
static bool node_fits_inside(Node *a, Node *b)
{
return bb_fits_inside(b->bb, b->bb + 3, a->bb, a->bb + 3);
}
template<class Node>
static void reorganize_find_fittest_parent(Node *tree, Node *node, std::pair<float, Node *> &cost)
{
std::queue<Node *> q;
q.push(tree);
while (!q.empty()) {
Node *parent = q.front();
q.pop();
if (parent == node) continue;
if (node_fits_inside(node, parent) && RE_rayobject_isAligned(parent->child) ) {
float pcost = bb_area(parent->bb, parent->bb + 3);
cost = std::min(cost, std::make_pair(pcost, parent) );
for (Node *child = parent->child; child; child = child->sibling)
q.push(child);
}
}
}
template<class Node>
static void reorganize(Node *root)
{
std::queue<Node *> q;
q.push(root);
while (!q.empty()) {
Node *node = q.front();
q.pop();
if (RE_rayobject_isAligned(node->child)) {
for (Node **prev = &node->child; *prev; ) {
assert(RE_rayobject_isAligned(*prev));
q.push(*prev);
std::pair<float, Node *> best(FLT_MAX, root);
reorganize_find_fittest_parent(root, *prev, best);
if (best.second == node) {
//Already inside the fitnest BB
prev = &(*prev)->sibling;
}
else {
Node *tmp = *prev;
*prev = (*prev)->sibling;
tmp->sibling = best.second->child;
best.second->child = tmp;
}
}
}
if (node != root) {
}
}
}
/*
* Prunes useless nodes from trees:
* erases nodes with total amount of primitives = 0
* prunes nodes with only one child (except if that child is a primitive)
*/
template<class Node>
static void remove_useless(Node *node, Node **new_node)
{
if (RE_rayobject_isAligned(node->child) ) {
for (Node **prev = &node->child; *prev; ) {
Node *next = (*prev)->sibling;
remove_useless(*prev, prev);
if (*prev == NULL)
*prev = next;
else {
(*prev)->sibling = next;
prev = &((*prev)->sibling);
}
}
}
if (node->child) {
if (RE_rayobject_isAligned(node->child) && node->child->sibling == 0)
*new_node = node->child;
}
else if (node->child == NULL) {
*new_node = NULL;
}
}
/*
* Minimizes expected number of BBtest by colapsing nodes
* it uses surface area heuristic for determining whether a node should be colapsed
*/
template<class Node>
static void pushup(Node *parent)
{
if (is_leaf(parent)) return;
float p_area = bb_area(parent->bb, parent->bb + 3);
Node **prev = &parent->child;
for (Node *child = parent->child; RE_rayobject_isAligned(child) && child; ) {
const float c_area = bb_area(child->bb, child->bb + 3);
const int nchilds = count_childs(child);
float original_cost = ((p_area != 0.0f) ? (c_area / p_area) * nchilds : 1.0f) + 1;
float flatten_cost = nchilds;
if (flatten_cost < original_cost && nchilds >= 2) {
append_sibling(child, child->child);
child = child->sibling;
*prev = child;
// *prev = child->child;
// append_sibling( *prev, child->sibling );
// child = *prev;
tot_pushup++;
}
else {
*prev = child;
prev = &(*prev)->sibling;
child = *prev;
}
}
for (Node *child = parent->child; RE_rayobject_isAligned(child) && child; child = child->sibling)
pushup(child);
}
/*
* try to optimize number of childs to be a multiple of SSize
*/
template<class Node, int SSize>
static void pushup_simd(Node *parent)
{
if (is_leaf(parent)) return;
int n = count_childs(parent);
Node **prev = &parent->child;
for (Node *child = parent->child; RE_rayobject_isAligned(child) && child; ) {
int cn = count_childs(child);
if (cn - 1 <= (SSize - (n % SSize) ) % SSize && RE_rayobject_isAligned(child->child) ) {
n += (cn - 1);
append_sibling(child, child->child);
child = child->sibling;
*prev = child;
}
else {
*prev = child;
prev = &(*prev)->sibling;
child = *prev;
}
}
for (Node *child = parent->child; RE_rayobject_isAligned(child) && child; child = child->sibling)
pushup_simd<Node, SSize>(child);
}
/*
* Pushdown
* makes sure no child fits inside any of its sibling
*/
template<class Node>
static void pushdown(Node *parent)
{
Node **s_child = &parent->child;
Node *child = parent->child;
while (child && RE_rayobject_isAligned(child)) {
Node *next = child->sibling;
Node **next_s_child = &child->sibling;
//assert(bb_fits_inside(parent->bb, parent->bb+3, child->bb, child->bb+3));
for (Node *i = parent->child; RE_rayobject_isAligned(i) && i; i = i->sibling)
if (child != i && bb_fits_inside(i->bb, i->bb + 3, child->bb, child->bb + 3) && RE_rayobject_isAligned(i->child)) {
// todo optimize (should the one with the smallest area?)
// float ia = bb_area(i->bb, i->bb+3)
// if (child->i)
*s_child = child->sibling;
child->sibling = i->child;
i->child = child;
next_s_child = s_child;
tot_pushdown++;
break;
}
child = next;
s_child = next_s_child;
}
for (Node *i = parent->child; RE_rayobject_isAligned(i) && i; i = i->sibling) {
pushdown(i);
}
}
/*
* BVH refit
* readjust nodes BB (useful if nodes childs where modified)
*/
template<class Node>
static float bvh_refit(Node *node)
{
if (is_leaf(node)) return 0;
if (is_leaf(node->child)) return 0;
float total = 0;
for (Node *child = node->child; child; child = child->sibling)
total += bvh_refit(child);
float old_area = bb_area(node->bb, node->bb + 3);
INIT_MINMAX(node->bb, node->bb + 3);
for (Node *child = node->child; child; child = child->sibling) {
DO_MIN(child->bb, node->bb);
DO_MAX(child->bb + 3, node->bb + 3);
}
total += old_area - bb_area(node->bb, node->bb + 3);
return total;
}
/*
* this finds the best way to packing a tree according to a given test cost function
* with the purpose to reduce the expected cost (eg.: number of BB tests).
*/
#include <vector>
#define MAX_CUT_SIZE 4 /* svbvh assumes max 4 children! */
#define MAX_OPTIMIZE_CHILDS MAX_CUT_SIZE
struct OVBVHNode {
float bb[6];
OVBVHNode *child;
OVBVHNode *sibling;
/*
* Returns min cost to represent the subtree starting at the given node,
* allowing it to have a given cutsize
*/
float cut_cost[MAX_CUT_SIZE];
float get_cost(int cutsize)
{
return cut_cost[cutsize - 1];
}
/*
* This saves the cut size of this child, when parent is reaching
* its minimum cut with the given cut size
*/
int cut_size[MAX_CUT_SIZE];
int get_cut_size(int parent_cut_size)
{
return cut_size[parent_cut_size - 1];
}
/*
* Reorganize the node based on calculated cut costs
*/
int best_cutsize;
void set_cut(int cutsize, OVBVHNode ***cut)
{
if (cutsize == 1) {
**cut = this;
*cut = &(**cut)->sibling;
}
else {
if (cutsize > MAX_CUT_SIZE) {
for (OVBVHNode *child = this->child; child && RE_rayobject_isAligned(child); child = child->sibling) {
child->set_cut(1, cut);
cutsize--;
}
assert(cutsize == 0);
}
else {
for (OVBVHNode *child = this->child; child && RE_rayobject_isAligned(child); child = child->sibling) {
child->set_cut(child->get_cut_size(cutsize), cut);
}
}
}
}
void optimize()
{
if (RE_rayobject_isAligned(this->child)) {
//Calc new childs
{
OVBVHNode **cut = &(this->child);
set_cut(best_cutsize, &cut);
*cut = NULL;
}
//Optimize new childs
for (OVBVHNode *child = this->child; child && RE_rayobject_isAligned(child); child = child->sibling)
child->optimize();
}
}
};
/*
* Calculates an optimal SIMD packing
*
*/
template<class Node, class TestCost>
struct VBVH_optimalPackSIMD {
TestCost testcost;
VBVH_optimalPackSIMD(TestCost testcost)
{
this->testcost = testcost;
}
/*
* calc best cut on a node
*/
struct calc_best {
Node *child[MAX_OPTIMIZE_CHILDS];
float child_hit_prob[MAX_OPTIMIZE_CHILDS];
calc_best(Node *node)
{
int nchilds = 0;
//Fetch childs and needed data
{
float parent_area = bb_area(node->bb, node->bb + 3);
for (Node *child = node->child; child && RE_rayobject_isAligned(child); child = child->sibling) {
this->child[nchilds] = child;
this->child_hit_prob[nchilds] = (parent_area != 0.0f) ? bb_area(child->bb, child->bb + 3) / parent_area : 1.0f;
nchilds++;
}
assert(nchilds >= 2 && nchilds <= MAX_OPTIMIZE_CHILDS);
}
//Build DP table to find minimum cost to represent this node with a given cutsize
int bt[MAX_OPTIMIZE_CHILDS + 1][MAX_CUT_SIZE + 1]; //backtrace table
float cost[MAX_OPTIMIZE_CHILDS + 1][MAX_CUT_SIZE + 1]; //cost table (can be reduced to float[2][MAX_CUT_COST])
for (int i = 0; i <= nchilds; i++) {
for (int j = 0; j <= MAX_CUT_SIZE; j++) {
cost[i][j] = INFINITY;
}
}
cost[0][0] = 0;
for (int i = 1; i <= nchilds; i++) {
for (int size = i - 1; size /*+(nchilds-i)*/ <= MAX_CUT_SIZE; size++) {
for (int cut = 1; cut + size /*+(nchilds-i)*/ <= MAX_CUT_SIZE; cut++) {
float new_cost = cost[i - 1][size] + child_hit_prob[i - 1] * child[i - 1]->get_cost(cut);
if (new_cost < cost[i][size + cut]) {
cost[i][size + cut] = new_cost;
bt[i][size + cut] = cut;
}
}
}
}
/* Save the ways to archive the minimum cost with a given cutsize */
for (int i = nchilds; i <= MAX_CUT_SIZE; i++) {
node->cut_cost[i - 1] = cost[nchilds][i];
if (cost[nchilds][i] < INFINITY) {
int current_size = i;
for (int j = nchilds; j > 0; j--) {
child[j - 1]->cut_size[i - 1] = bt[j][current_size];
current_size -= bt[j][current_size];
}
}
}
}
};
void calc_costs(Node *node)
{
if (RE_rayobject_isAligned(node->child) ) {
int nchilds = 0;
for (Node *child = node->child; child && RE_rayobject_isAligned(child); child = child->sibling) {
calc_costs(child);
nchilds++;
}
for (int i = 0; i < MAX_CUT_SIZE; i++)
node->cut_cost[i] = INFINITY;
//We are not allowed to look on nodes with with so many childs
if (nchilds > MAX_CUT_SIZE) {
float cost = 0;
float parent_area = bb_area(node->bb, node->bb + 3);
for (Node *child = node->child; child && RE_rayobject_isAligned(child); child = child->sibling) {
cost += ((parent_area != 0.0f) ? (bb_area(child->bb, child->bb + 3) / parent_area) : 1.0f) * child->get_cost(1);
}
cost += testcost(nchilds);
node->cut_cost[0] = cost;
node->best_cutsize = nchilds;
}
else {
calc_best calc(node);
//calc expected cost if we optimaly pack this node
for (int cutsize = nchilds; cutsize <= MAX_CUT_SIZE; cutsize++) {
float m = node->get_cost(cutsize) + testcost(cutsize);
if (m < node->cut_cost[0]) {
node->cut_cost[0] = m;
node->best_cutsize = cutsize;
}
}
}
assert(node->cut_cost[0] != INFINITY);
}
else {
node->cut_cost[0] = 1.0f;
for (int i = 1; i < MAX_CUT_SIZE; i++)
node->cut_cost[i] = INFINITY;
}
}
Node *transform(Node *node)
{
if (RE_rayobject_isAligned(node->child)) {
#ifdef DEBUG
static int num = 0;
bool first = false;
if (num == 0) { num++; first = true; }
#endif
calc_costs(node);
#ifdef DEBUG
if (first) {
printf("expected cost = %f (%d)\n", node->cut_cost[0], node->best_cutsize);
}
#endif
node->optimize();
}
return node;
}
};