Hair Particle: Fix issue on OSX due to hardware accelerated subdivision

Workaround to tranform feedback not working on mac.
On some system it crashes (see T58489) and on some other it outputs
garbage (see T60171).

So instead of using transform feedback we render to a texture,
readback the result to system memory and reupload as VBO data.
It is really not ideal performance wise, but it is the simplest
and the most local workaround that still uses the power of the GPU.

This should fix T59426, T60171 and T58489.
This commit is contained in:
Clément Foucault
2019-02-28 22:56:27 +01:00
parent fbd8c7dc4e
commit 680c7209ec
2 changed files with 113 additions and 4 deletions

View File

@@ -40,16 +40,33 @@
#include "draw_hair_private.h"
#ifndef __APPLE__
# define USE_TRANSFORM_FEEDBACK
#endif
typedef enum ParticleRefineShader {
PART_REFINE_CATMULL_ROM = 0,
PART_REFINE_MAX_SHADER,
} ParticleRefineShader;
#ifndef USE_TRANSFORM_FEEDBACK
typedef struct ParticleRefineCall {
struct ParticleRefineCall *next;
GPUVertBuf *vbo;
DRWShadingGroup *shgrp;
uint vert_len;
} ParticleRefineCall;
static ParticleRefineCall *g_tf_calls = NULL;
static int g_tf_target_height;
#endif
static GPUShader *g_refine_shaders[PART_REFINE_MAX_SHADER] = {NULL};
static DRWPass *g_tf_pass; /* XXX can be a problem with multiple DRWManager in the future */
extern char datatoc_common_hair_lib_glsl[];
extern char datatoc_common_hair_refine_vert_glsl[];
extern char datatoc_gpu_shader_3D_smooth_color_frag_glsl[];
static GPUShader *hair_refine_shader_get(ParticleRefineShader sh)
{
@@ -59,10 +76,17 @@ static GPUShader *hair_refine_shader_get(ParticleRefineShader sh)
char *vert_with_lib = BLI_string_joinN(datatoc_common_hair_lib_glsl, datatoc_common_hair_refine_vert_glsl);
const char *var_names[1] = {"outData"};
#ifdef USE_TRANSFORM_FEEDBACK
const char *var_names[1] = {"finalColor"};
g_refine_shaders[sh] = DRW_shader_create_with_transform_feedback(vert_with_lib, NULL, "#define HAIR_PHASE_SUBDIV\n",
GPU_SHADER_TFB_POINTS, var_names, 1);
#else
g_refine_shaders[sh] = DRW_shader_create(
vert_with_lib, NULL,
datatoc_gpu_shader_3D_smooth_color_frag_glsl,
"#define HAIR_PHASE_SUBDIV\n"
"#define TF_WORKAROUND\n");
#endif
MEM_freeN(vert_with_lib);
@@ -71,7 +95,11 @@ static GPUShader *hair_refine_shader_get(ParticleRefineShader sh)
void DRW_hair_init(void)
{
#ifdef USE_TRANSFORM_FEEDBACK
g_tf_pass = DRW_pass_create("Update Hair Pass", DRW_STATE_TRANS_FEEDBACK);
#else
g_tf_pass = DRW_pass_create("Update Hair Pass", DRW_STATE_WRITE_COLOR | DRW_STATE_POINT);
#endif
}
typedef struct DRWHairInstanceData {
@@ -162,8 +190,22 @@ static DRWShadingGroup *drw_shgroup_create_hair_procedural_ex(
if (need_ft_update) {
int final_points_len = hair_cache->final[subdiv].strands_res * hair_cache->strands_len;
GPUShader *tf_shader = hair_refine_shader_get(PART_REFINE_CATMULL_ROM);
#ifdef USE_TRANSFORM_FEEDBACK
DRWShadingGroup *tf_shgrp = DRW_shgroup_transform_feedback_create(tf_shader, g_tf_pass,
hair_cache->final[subdiv].proc_buf);
#else
DRWShadingGroup *tf_shgrp = DRW_shgroup_create(tf_shader, g_tf_pass);
ParticleRefineCall *pr_call = MEM_mallocN(sizeof(*pr_call), __func__);
pr_call->next = g_tf_calls;
pr_call->vbo = hair_cache->final[subdiv].proc_buf;
pr_call->shgrp = tf_shgrp;
pr_call->vert_len = final_points_len;
g_tf_calls = pr_call;
DRW_shgroup_uniform_int(tf_shgrp, "targetHeight", &g_tf_target_height, 1);
#endif
DRW_shgroup_uniform_texture(tf_shgrp, "hairPointBuffer", hair_cache->point_tex);
DRW_shgroup_uniform_texture(tf_shgrp, "hairStrandBuffer", hair_cache->strand_tex);
DRW_shgroup_uniform_int(tf_shgrp, "hairStrandsRes", &hair_cache->final[subdiv].strands_res, 1);
@@ -191,7 +233,61 @@ DRWShadingGroup *DRW_shgroup_material_hair_create(
void DRW_hair_update(void)
{
#ifndef USE_TRANSFORM_FEEDBACK
/**
* Workaround to tranform feedback not working on mac.
* On some system it crashes (see T58489) and on some other it renders garbage (see T60171).
*
* So instead of using transform feedback we render to a texture,
* readback the result to system memory and reupload as VBO data.
* It is really not ideal performance wise, but it is the simplest
* and the most local workaround that still uses the power of the GPU.
**/
if (g_tf_calls == NULL) {
return;
}
/* Search ideal buffer size. */
uint max_size = 0;
for (ParticleRefineCall *pr_call = g_tf_calls; pr_call; pr_call = pr_call->next) {
max_size = max_ii(max_size, pr_call->vert_len);
}
/* Create target Texture / Framebuffer */
int height = (1 + max_size / 8192);
GPUTexture *tex = DRW_texture_pool_query_2D(8192, height, GPU_RGBA32F, (void *)DRW_hair_update);
g_tf_target_height = height;
GPUFrameBuffer *fb = NULL;
GPU_framebuffer_ensure_config(&fb, {
GPU_ATTACHMENT_NONE,
GPU_ATTACHMENT_TEXTURE(tex),
});
float *data = MEM_mallocN(sizeof(float) * 4 * 8192 * height, "tf fallback buffer");
GPU_framebuffer_bind(fb);
while (g_tf_calls != NULL) {
ParticleRefineCall *pr_call = g_tf_calls;
g_tf_calls = g_tf_calls->next;
DRW_draw_pass_subset(g_tf_pass, pr_call->shgrp, pr_call->shgrp);
/* Readback result to main memory. */
GPU_framebuffer_read_color(fb, 0, 0, 8192, height, 4, 0, data);
/* Upload back to VBO. */
GPU_vertbuf_use(pr_call->vbo);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float) * 4 * pr_call->vert_len, data);
MEM_freeN(pr_call);
}
MEM_freeN(data);
GPU_framebuffer_free(fb);
#else
/* TODO(fclem): replace by compute shader. */
/* Just render using transform feedback. */
DRW_draw_pass(g_tf_pass);
#endif
}
void DRW_hair_free(void)

View File

@@ -1,7 +1,7 @@
/* To be compiled with common_hair_lib.glsl */
out vec4 outData;
out vec4 finalColor;
vec4 get_weights_cardinal(float t)
{
@@ -44,6 +44,10 @@ vec4 interp_data(vec4 v0, vec4 v1, vec4 v2, vec4 v3, vec4 w)
return v0 * w.x + v1 * w.y + v2 * w.z + v3 * w.w;
}
#ifdef TF_WORKAROUND
uniform int targetHeight;
#endif
void main(void)
{
float interp_time;
@@ -51,5 +55,14 @@ void main(void)
hair_get_interp_attrs(data0, data1, data2, data3, interp_time);
vec4 weights = get_weights_cardinal(interp_time);
outData = interp_data(data0, data1, data2, data3, weights);
finalColor = interp_data(data0, data1, data2, data3, weights);
#ifdef TF_WORKAROUND
gl_Position.x = ((float(gl_VertexID % 8192) + 0.5) / 8192.0) * 2.0 - 1.0;
gl_Position.y = ((float(gl_VertexID / 8192) + 0.5) / float(targetHeight)) * 2.0 - 1.0;
gl_Position.z = 0.0;
gl_Position.w = 1.0;
gl_PointSize = 1.0;
#endif
}