In a recent update to the fluids modifier (rB03c2439d96e8), I introduced a flush call that sets all grids to NULL if the frame is outside of the allowed frame range. This way, the texture creation function must also check if the data grid is NULL before trying to create a texture. Reviewed By: fclem Differential Revision: https://developer.blender.org/D8872
504 lines
14 KiB
C
504 lines
14 KiB
C
/*
|
|
* 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) 2005 Blender Foundation.
|
|
* All rights reserved.
|
|
*/
|
|
|
|
/** \file
|
|
* \ingroup gpu
|
|
*
|
|
* GPU fluid drawing functions.
|
|
*/
|
|
|
|
#include <string.h>
|
|
|
|
#include "BLI_math.h"
|
|
#include "BLI_utildefines.h"
|
|
|
|
#include "DNA_fluid_types.h"
|
|
#include "DNA_modifier_types.h"
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "BKE_colorband.h"
|
|
|
|
#include "GPU_texture.h"
|
|
|
|
#include "draw_common.h" /* Own include. */
|
|
|
|
#ifdef WITH_FLUID
|
|
# include "manta_fluid_API.h"
|
|
#endif
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Private API
|
|
* \{ */
|
|
|
|
#ifdef WITH_FLUID
|
|
|
|
enum {
|
|
TFUNC_FLAME_SPECTRUM = 0,
|
|
TFUNC_COLOR_RAMP = 1,
|
|
};
|
|
|
|
# define TFUNC_WIDTH 256
|
|
|
|
static void create_flame_spectrum_texture(float *data)
|
|
{
|
|
# define FIRE_THRESH 7
|
|
# define MAX_FIRE_ALPHA 0.06f
|
|
# define FULL_ON_FIRE 100
|
|
|
|
float *spec_pixels = (float *)MEM_mallocN(TFUNC_WIDTH * 4 * 16 * 16 * sizeof(float),
|
|
"spec_pixels");
|
|
|
|
blackbody_temperature_to_rgb_table(data, TFUNC_WIDTH, 1500, 3000);
|
|
|
|
for (int i = 0; i < 16; i++) {
|
|
for (int j = 0; j < 16; j++) {
|
|
for (int k = 0; k < TFUNC_WIDTH; k++) {
|
|
int index = (j * TFUNC_WIDTH * 16 + i * TFUNC_WIDTH + k) * 4;
|
|
if (k >= FIRE_THRESH) {
|
|
spec_pixels[index] = (data[k * 4]);
|
|
spec_pixels[index + 1] = (data[k * 4 + 1]);
|
|
spec_pixels[index + 2] = (data[k * 4 + 2]);
|
|
spec_pixels[index + 3] = MAX_FIRE_ALPHA *
|
|
((k > FULL_ON_FIRE) ?
|
|
1.0f :
|
|
(k - FIRE_THRESH) / ((float)FULL_ON_FIRE - FIRE_THRESH));
|
|
}
|
|
else {
|
|
zero_v4(&spec_pixels[index]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
memcpy(data, spec_pixels, sizeof(float) * 4 * TFUNC_WIDTH);
|
|
|
|
MEM_freeN(spec_pixels);
|
|
|
|
# undef FIRE_THRESH
|
|
# undef MAX_FIRE_ALPHA
|
|
# undef FULL_ON_FIRE
|
|
}
|
|
|
|
static void create_color_ramp(const struct ColorBand *coba, float *data)
|
|
{
|
|
for (int i = 0; i < TFUNC_WIDTH; i++) {
|
|
BKE_colorband_evaluate(coba, (float)i / TFUNC_WIDTH, &data[i * 4]);
|
|
straight_to_premul_v4(&data[i * 4]);
|
|
}
|
|
}
|
|
|
|
static GPUTexture *create_transfer_function(int type, const struct ColorBand *coba)
|
|
{
|
|
float *data = (float *)MEM_mallocN(sizeof(float[4]) * TFUNC_WIDTH, __func__);
|
|
|
|
switch (type) {
|
|
case TFUNC_FLAME_SPECTRUM:
|
|
create_flame_spectrum_texture(data);
|
|
break;
|
|
case TFUNC_COLOR_RAMP:
|
|
create_color_ramp(coba, data);
|
|
break;
|
|
}
|
|
|
|
GPUTexture *tex = GPU_texture_create_1d("transf_func", TFUNC_WIDTH, 1, GPU_SRGB8_A8, data);
|
|
|
|
MEM_freeN(data);
|
|
|
|
return tex;
|
|
}
|
|
|
|
static void swizzle_texture_channel_single(GPUTexture *tex)
|
|
{
|
|
/* Swizzle texture channels so that we get useful RGBA values when sampling
|
|
* a texture with fewer channels, e.g. when using density as color. */
|
|
GPU_texture_swizzle_set(tex, "rrr1");
|
|
}
|
|
|
|
static float *rescale_3d(const int dim[3],
|
|
const int final_dim[3],
|
|
int channels,
|
|
const float *fpixels)
|
|
{
|
|
const uint w = dim[0], h = dim[1], d = dim[2];
|
|
const uint fw = final_dim[0], fh = final_dim[1], fd = final_dim[2];
|
|
const uint xf = w / fw, yf = h / fh, zf = d / fd;
|
|
const uint pixel_count = fw * fh * fd;
|
|
float *nfpixels = (float *)MEM_mallocN(channels * sizeof(float) * pixel_count, __func__);
|
|
|
|
if (nfpixels) {
|
|
printf("Performance: You need to scale a 3D texture, feel the pain!\n");
|
|
|
|
for (uint k = 0; k < fd; k++) {
|
|
for (uint j = 0; j < fh; j++) {
|
|
for (uint i = 0; i < fw; i++) {
|
|
/* Obviously doing nearest filtering here,
|
|
* it's going to be slow in any case, let's not make it worse. */
|
|
float xb = i * xf;
|
|
float yb = j * yf;
|
|
float zb = k * zf;
|
|
uint offset = k * (fw * fh) + i * fh + j;
|
|
uint offset_orig = (zb) * (w * h) + (xb)*h + (yb);
|
|
|
|
if (channels == 4) {
|
|
nfpixels[offset * 4] = fpixels[offset_orig * 4];
|
|
nfpixels[offset * 4 + 1] = fpixels[offset_orig * 4 + 1];
|
|
nfpixels[offset * 4 + 2] = fpixels[offset_orig * 4 + 2];
|
|
nfpixels[offset * 4 + 3] = fpixels[offset_orig * 4 + 3];
|
|
}
|
|
else if (channels == 1) {
|
|
nfpixels[offset] = fpixels[offset_orig];
|
|
}
|
|
else {
|
|
BLI_assert(0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nfpixels;
|
|
}
|
|
|
|
/* Will resize input to fit GL system limits. */
|
|
static GPUTexture *create_volume_texture(const int dim[3],
|
|
eGPUTextureFormat format,
|
|
const float *data)
|
|
{
|
|
GPUTexture *tex = NULL;
|
|
int final_dim[3] = {UNPACK3(dim)};
|
|
|
|
if (data == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
while (1) {
|
|
tex = GPU_texture_create_3d("volume", UNPACK3(final_dim), 1, format, NULL);
|
|
|
|
if (tex != NULL) {
|
|
break;
|
|
}
|
|
|
|
if (final_dim[0] == 1 && final_dim[1] == 1 && final_dim[2] == 1) {
|
|
break;
|
|
}
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
final_dim[i] = max_ii(1, final_dim[i] / 2);
|
|
}
|
|
}
|
|
|
|
if (tex == NULL) {
|
|
printf("Error: Could not create 3D texture.\n");
|
|
tex = GPU_texture_create_error(3, false);
|
|
}
|
|
else if (equals_v3v3_int(dim, final_dim)) {
|
|
/* No need to resize, just upload the data. */
|
|
GPU_texture_update_sub(tex, GPU_DATA_FLOAT, data, 0, 0, 0, UNPACK3(final_dim));
|
|
}
|
|
else {
|
|
/* We need to resize the input. */
|
|
int channels = (format == GPU_R8) ? 1 : 4;
|
|
float *rescaled_data = rescale_3d(dim, final_dim, channels, data);
|
|
if (rescaled_data) {
|
|
GPU_texture_update_sub(tex, GPU_DATA_FLOAT, rescaled_data, 0, 0, 0, UNPACK3(final_dim));
|
|
MEM_freeN(rescaled_data);
|
|
}
|
|
else {
|
|
printf("Error: Could not allocate rescaled 3d texture!\n");
|
|
GPU_texture_free(tex);
|
|
tex = GPU_texture_create_error(3, false);
|
|
}
|
|
}
|
|
return tex;
|
|
}
|
|
|
|
static GPUTexture *create_field_texture(FluidDomainSettings *fds)
|
|
{
|
|
float *field = NULL;
|
|
|
|
switch (fds->coba_field) {
|
|
case FLUID_DOMAIN_FIELD_DENSITY:
|
|
field = manta_smoke_get_density(fds->fluid);
|
|
break;
|
|
case FLUID_DOMAIN_FIELD_HEAT:
|
|
field = manta_smoke_get_heat(fds->fluid);
|
|
break;
|
|
case FLUID_DOMAIN_FIELD_FUEL:
|
|
field = manta_smoke_get_fuel(fds->fluid);
|
|
break;
|
|
case FLUID_DOMAIN_FIELD_REACT:
|
|
field = manta_smoke_get_react(fds->fluid);
|
|
break;
|
|
case FLUID_DOMAIN_FIELD_FLAME:
|
|
field = manta_smoke_get_flame(fds->fluid);
|
|
break;
|
|
case FLUID_DOMAIN_FIELD_VELOCITY_X:
|
|
field = manta_get_velocity_x(fds->fluid);
|
|
break;
|
|
case FLUID_DOMAIN_FIELD_VELOCITY_Y:
|
|
field = manta_get_velocity_y(fds->fluid);
|
|
break;
|
|
case FLUID_DOMAIN_FIELD_VELOCITY_Z:
|
|
field = manta_get_velocity_z(fds->fluid);
|
|
break;
|
|
case FLUID_DOMAIN_FIELD_COLOR_R:
|
|
field = manta_smoke_get_color_r(fds->fluid);
|
|
break;
|
|
case FLUID_DOMAIN_FIELD_COLOR_G:
|
|
field = manta_smoke_get_color_g(fds->fluid);
|
|
break;
|
|
case FLUID_DOMAIN_FIELD_COLOR_B:
|
|
field = manta_smoke_get_color_b(fds->fluid);
|
|
break;
|
|
case FLUID_DOMAIN_FIELD_FORCE_X:
|
|
field = manta_get_force_x(fds->fluid);
|
|
break;
|
|
case FLUID_DOMAIN_FIELD_FORCE_Y:
|
|
field = manta_get_force_y(fds->fluid);
|
|
break;
|
|
case FLUID_DOMAIN_FIELD_FORCE_Z:
|
|
field = manta_get_force_z(fds->fluid);
|
|
break;
|
|
default:
|
|
return NULL;
|
|
}
|
|
|
|
GPUTexture *tex = create_volume_texture(fds->res, GPU_R8, field);
|
|
swizzle_texture_channel_single(tex);
|
|
return tex;
|
|
}
|
|
|
|
static GPUTexture *create_density_texture(FluidDomainSettings *fds, int highres)
|
|
{
|
|
int *dim = (highres) ? fds->res_noise : fds->res;
|
|
|
|
float *data;
|
|
if (highres) {
|
|
data = manta_noise_get_density(fds->fluid);
|
|
}
|
|
else {
|
|
data = manta_smoke_get_density(fds->fluid);
|
|
}
|
|
|
|
if (data == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
GPUTexture *tex = create_volume_texture(dim, GPU_R8, data);
|
|
swizzle_texture_channel_single(tex);
|
|
return tex;
|
|
}
|
|
|
|
static GPUTexture *create_color_texture(FluidDomainSettings *fds, int highres)
|
|
{
|
|
const bool has_color = (highres) ? manta_noise_has_colors(fds->fluid) :
|
|
manta_smoke_has_colors(fds->fluid);
|
|
|
|
if (!has_color) {
|
|
return NULL;
|
|
}
|
|
|
|
int cell_count = (highres) ? manta_noise_get_cells(fds->fluid) : fds->total_cells;
|
|
int *dim = (highres) ? fds->res_noise : fds->res;
|
|
float *data = (float *)MEM_callocN(sizeof(float) * cell_count * 4, "smokeColorTexture");
|
|
|
|
if (data == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
if (highres) {
|
|
manta_noise_get_rgba(fds->fluid, data, 0);
|
|
}
|
|
else {
|
|
manta_smoke_get_rgba(fds->fluid, data, 0);
|
|
}
|
|
|
|
GPUTexture *tex = create_volume_texture(dim, GPU_RGBA8, data);
|
|
|
|
MEM_freeN(data);
|
|
|
|
return tex;
|
|
}
|
|
|
|
static GPUTexture *create_flame_texture(FluidDomainSettings *fds, int highres)
|
|
{
|
|
float *source = NULL;
|
|
const bool has_fuel = (highres) ? manta_noise_has_fuel(fds->fluid) :
|
|
manta_smoke_has_fuel(fds->fluid);
|
|
int *dim = (highres) ? fds->res_noise : fds->res;
|
|
|
|
if (!has_fuel) {
|
|
return NULL;
|
|
}
|
|
|
|
if (highres) {
|
|
source = manta_noise_get_flame(fds->fluid);
|
|
}
|
|
else {
|
|
source = manta_smoke_get_flame(fds->fluid);
|
|
}
|
|
|
|
GPUTexture *tex = create_volume_texture(dim, GPU_R8, source);
|
|
swizzle_texture_channel_single(tex);
|
|
return tex;
|
|
}
|
|
|
|
#endif /* WITH_FLUID */
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Public API
|
|
* \{ */
|
|
|
|
void DRW_smoke_free(FluidModifierData *fmd)
|
|
{
|
|
if (fmd->type & MOD_FLUID_TYPE_DOMAIN && fmd->domain) {
|
|
if (fmd->domain->tex_density) {
|
|
GPU_texture_free(fmd->domain->tex_density);
|
|
fmd->domain->tex_density = NULL;
|
|
}
|
|
|
|
if (fmd->domain->tex_color) {
|
|
GPU_texture_free(fmd->domain->tex_color);
|
|
fmd->domain->tex_color = NULL;
|
|
}
|
|
|
|
if (fmd->domain->tex_shadow) {
|
|
GPU_texture_free(fmd->domain->tex_shadow);
|
|
fmd->domain->tex_shadow = NULL;
|
|
}
|
|
|
|
if (fmd->domain->tex_flame) {
|
|
GPU_texture_free(fmd->domain->tex_flame);
|
|
fmd->domain->tex_flame = NULL;
|
|
}
|
|
|
|
if (fmd->domain->tex_flame_coba) {
|
|
GPU_texture_free(fmd->domain->tex_flame_coba);
|
|
fmd->domain->tex_flame_coba = NULL;
|
|
}
|
|
|
|
if (fmd->domain->tex_coba) {
|
|
GPU_texture_free(fmd->domain->tex_coba);
|
|
fmd->domain->tex_coba = NULL;
|
|
}
|
|
|
|
if (fmd->domain->tex_field) {
|
|
GPU_texture_free(fmd->domain->tex_field);
|
|
fmd->domain->tex_field = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
void DRW_smoke_ensure_coba_field(FluidModifierData *fmd)
|
|
{
|
|
#ifndef WITH_FLUID
|
|
UNUSED_VARS(fmd);
|
|
#else
|
|
if (fmd->type & MOD_FLUID_TYPE_DOMAIN) {
|
|
FluidDomainSettings *fds = fmd->domain;
|
|
|
|
if (!fds->tex_field) {
|
|
fds->tex_field = create_field_texture(fds);
|
|
}
|
|
if (!fds->tex_coba) {
|
|
fds->tex_coba = create_transfer_function(TFUNC_COLOR_RAMP, fds->coba);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void DRW_smoke_ensure(FluidModifierData *fmd, int highres)
|
|
{
|
|
#ifndef WITH_FLUID
|
|
UNUSED_VARS(fmd, highres);
|
|
#else
|
|
if (fmd->type & MOD_FLUID_TYPE_DOMAIN) {
|
|
FluidDomainSettings *fds = fmd->domain;
|
|
|
|
if (!fds->tex_density) {
|
|
fds->tex_density = create_density_texture(fds, highres);
|
|
}
|
|
if (!fds->tex_color) {
|
|
fds->tex_color = create_color_texture(fds, highres);
|
|
}
|
|
if (!fds->tex_flame) {
|
|
fds->tex_flame = create_flame_texture(fds, highres);
|
|
}
|
|
if (!fds->tex_flame_coba && fds->tex_flame) {
|
|
fds->tex_flame_coba = create_transfer_function(TFUNC_FLAME_SPECTRUM, NULL);
|
|
}
|
|
if (!fds->tex_shadow) {
|
|
fds->tex_shadow = create_volume_texture(
|
|
fds->res, GPU_R8, manta_smoke_get_shadow(fds->fluid));
|
|
}
|
|
}
|
|
#endif /* WITH_FLUID */
|
|
}
|
|
|
|
void DRW_smoke_ensure_velocity(FluidModifierData *fmd)
|
|
{
|
|
#ifndef WITH_FLUID
|
|
UNUSED_VARS(fmd);
|
|
#else
|
|
if (fmd->type & MOD_FLUID_TYPE_DOMAIN) {
|
|
FluidDomainSettings *fds = fmd->domain;
|
|
|
|
const float *vel_x = manta_get_velocity_x(fds->fluid);
|
|
const float *vel_y = manta_get_velocity_y(fds->fluid);
|
|
const float *vel_z = manta_get_velocity_z(fds->fluid);
|
|
|
|
if (ELEM(NULL, vel_x, vel_y, vel_z)) {
|
|
return;
|
|
}
|
|
|
|
if (!fds->tex_velocity_x) {
|
|
fds->tex_velocity_x = GPU_texture_create_3d("velx", UNPACK3(fds->res), 1, GPU_R16F, vel_x);
|
|
fds->tex_velocity_y = GPU_texture_create_3d("vely", UNPACK3(fds->res), 1, GPU_R16F, vel_y);
|
|
fds->tex_velocity_z = GPU_texture_create_3d("velz", UNPACK3(fds->res), 1, GPU_R16F, vel_z);
|
|
}
|
|
}
|
|
#endif /* WITH_FLUID */
|
|
}
|
|
|
|
/* TODO Unify with the other DRW_smoke_free. */
|
|
void DRW_smoke_free_velocity(FluidModifierData *fmd)
|
|
{
|
|
if (fmd->type & MOD_FLUID_TYPE_DOMAIN && fmd->domain) {
|
|
if (fmd->domain->tex_velocity_x) {
|
|
GPU_texture_free(fmd->domain->tex_velocity_x);
|
|
}
|
|
|
|
if (fmd->domain->tex_velocity_y) {
|
|
GPU_texture_free(fmd->domain->tex_velocity_y);
|
|
}
|
|
|
|
if (fmd->domain->tex_velocity_z) {
|
|
GPU_texture_free(fmd->domain->tex_velocity_z);
|
|
}
|
|
|
|
fmd->domain->tex_velocity_x = NULL;
|
|
fmd->domain->tex_velocity_y = NULL;
|
|
fmd->domain->tex_velocity_z = NULL;
|
|
}
|
|
}
|
|
|
|
/** \} */
|