mirror of
https://github.com/reactos/wine.git
synced 2024-11-26 05:00:30 +00:00
wined3d: Move tex_unit_map and friends into the context.
This commit is contained in:
parent
f29c24f2de
commit
112617f00b
@ -4574,7 +4574,7 @@ static void find_arb_vs_compile_args(const struct wined3d_state *state,
|
||||
const struct wined3d_context *context, const struct wined3d_shader *shader,
|
||||
struct arb_vs_compile_args *args)
|
||||
{
|
||||
struct wined3d_device *device = shader->device;
|
||||
const struct wined3d_device *device = shader->device;
|
||||
const struct wined3d_adapter *adapter = device->adapter;
|
||||
const struct wined3d_gl_info *gl_info = context->gl_info;
|
||||
const struct wined3d_d3d_info *d3d_info = context->d3d_info;
|
||||
@ -4618,9 +4618,9 @@ static void find_arb_vs_compile_args(const struct wined3d_state *state,
|
||||
args->clip.boolclip.bools |= ( 1 << i);
|
||||
}
|
||||
|
||||
args->vertex.samplers[0] = device->texUnitMap[MAX_FRAGMENT_SAMPLERS + 0];
|
||||
args->vertex.samplers[1] = device->texUnitMap[MAX_FRAGMENT_SAMPLERS + 1];
|
||||
args->vertex.samplers[2] = device->texUnitMap[MAX_FRAGMENT_SAMPLERS + 2];
|
||||
args->vertex.samplers[0] = context->tex_unit_map[MAX_FRAGMENT_SAMPLERS + 0];
|
||||
args->vertex.samplers[1] = context->tex_unit_map[MAX_FRAGMENT_SAMPLERS + 1];
|
||||
args->vertex.samplers[2] = context->tex_unit_map[MAX_FRAGMENT_SAMPLERS + 2];
|
||||
args->vertex.samplers[3] = 0;
|
||||
|
||||
/* Skip if unused or local */
|
||||
|
@ -874,7 +874,7 @@ static void set_tex_op_atifs(struct wined3d_context *context, const struct wined
|
||||
*/
|
||||
for (i = 0; i < desc->num_textures_used; ++i)
|
||||
{
|
||||
mapped_stage = device->texUnitMap[i];
|
||||
mapped_stage = context->tex_unit_map[i];
|
||||
if (mapped_stage != WINED3D_UNMAPPED_STAGE)
|
||||
{
|
||||
context_active_texture(context, gl_info, mapped_stage);
|
||||
|
@ -1350,6 +1350,21 @@ struct wined3d_context *context_create(struct wined3d_swapchain *swapchain,
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Initialize the texture unit mapping to a 1:1 mapping */
|
||||
for (s = 0; s < MAX_COMBINED_SAMPLERS; ++s)
|
||||
{
|
||||
if (s < gl_info->limits.fragment_samplers)
|
||||
{
|
||||
ret->tex_unit_map[s] = s;
|
||||
ret->rev_tex_unit_map[s] = s;
|
||||
}
|
||||
else
|
||||
{
|
||||
ret->tex_unit_map[s] = WINED3D_UNMAPPED_STAGE;
|
||||
ret->rev_tex_unit_map[s] = WINED3D_UNMAPPED_STAGE;
|
||||
}
|
||||
}
|
||||
|
||||
if (!(hdc = GetDC(swapchain->win_handle)))
|
||||
{
|
||||
WARN("Failed to retireve device context, trying swapchain backup.\n");
|
||||
@ -1772,7 +1787,7 @@ static void SetupForBlit(const struct wined3d_device *device, struct wined3d_con
|
||||
*/
|
||||
for (i = gl_info->limits.textures - 1; i > 0 ; --i)
|
||||
{
|
||||
sampler = device->rev_tex_unit_map[i];
|
||||
sampler = context->rev_tex_unit_map[i];
|
||||
context_active_texture(context, gl_info, i);
|
||||
|
||||
if (gl_info->supported[ARB_TEXTURE_CUBE_MAP])
|
||||
@ -1802,7 +1817,7 @@ static void SetupForBlit(const struct wined3d_device *device, struct wined3d_con
|
||||
}
|
||||
context_active_texture(context, gl_info, 0);
|
||||
|
||||
sampler = device->rev_tex_unit_map[0];
|
||||
sampler = context->rev_tex_unit_map[0];
|
||||
|
||||
if (gl_info->supported[ARB_TEXTURE_CUBE_MAP])
|
||||
{
|
||||
@ -2328,6 +2343,221 @@ void context_state_fb(struct wined3d_context *context, const struct wined3d_stat
|
||||
}
|
||||
}
|
||||
|
||||
static void context_map_stage(struct wined3d_context *context, DWORD stage, DWORD unit)
|
||||
{
|
||||
DWORD i = context->rev_tex_unit_map[unit];
|
||||
DWORD j = context->tex_unit_map[stage];
|
||||
|
||||
context->tex_unit_map[stage] = unit;
|
||||
if (i != WINED3D_UNMAPPED_STAGE && i != stage)
|
||||
context->tex_unit_map[i] = WINED3D_UNMAPPED_STAGE;
|
||||
|
||||
context->rev_tex_unit_map[unit] = stage;
|
||||
if (j != WINED3D_UNMAPPED_STAGE && j != unit)
|
||||
context->rev_tex_unit_map[j] = WINED3D_UNMAPPED_STAGE;
|
||||
}
|
||||
|
||||
static void context_invalidate_texture_stage(struct wined3d_context *context, DWORD stage)
|
||||
{
|
||||
DWORD i;
|
||||
|
||||
for (i = 0; i <= WINED3D_HIGHEST_TEXTURE_STATE; ++i)
|
||||
context_invalidate_state(context, STATE_TEXTURESTAGE(stage, i));
|
||||
}
|
||||
|
||||
static void context_update_fixed_function_usage_map(struct wined3d_context *context,
|
||||
const struct wined3d_state *state)
|
||||
{
|
||||
UINT i;
|
||||
|
||||
context->fixed_function_usage_map = 0;
|
||||
for (i = 0; i < MAX_TEXTURES; ++i)
|
||||
{
|
||||
enum wined3d_texture_op color_op = state->texture_states[i][WINED3D_TSS_COLOR_OP];
|
||||
enum wined3d_texture_op alpha_op = state->texture_states[i][WINED3D_TSS_ALPHA_OP];
|
||||
DWORD color_arg1 = state->texture_states[i][WINED3D_TSS_COLOR_ARG1] & WINED3DTA_SELECTMASK;
|
||||
DWORD color_arg2 = state->texture_states[i][WINED3D_TSS_COLOR_ARG2] & WINED3DTA_SELECTMASK;
|
||||
DWORD color_arg3 = state->texture_states[i][WINED3D_TSS_COLOR_ARG0] & WINED3DTA_SELECTMASK;
|
||||
DWORD alpha_arg1 = state->texture_states[i][WINED3D_TSS_ALPHA_ARG1] & WINED3DTA_SELECTMASK;
|
||||
DWORD alpha_arg2 = state->texture_states[i][WINED3D_TSS_ALPHA_ARG2] & WINED3DTA_SELECTMASK;
|
||||
DWORD alpha_arg3 = state->texture_states[i][WINED3D_TSS_ALPHA_ARG0] & WINED3DTA_SELECTMASK;
|
||||
|
||||
/* Not used, and disable higher stages. */
|
||||
if (color_op == WINED3D_TOP_DISABLE)
|
||||
break;
|
||||
|
||||
if (((color_arg1 == WINED3DTA_TEXTURE) && color_op != WINED3D_TOP_SELECT_ARG2)
|
||||
|| ((color_arg2 == WINED3DTA_TEXTURE) && color_op != WINED3D_TOP_SELECT_ARG1)
|
||||
|| ((color_arg3 == WINED3DTA_TEXTURE)
|
||||
&& (color_op == WINED3D_TOP_MULTIPLY_ADD || color_op == WINED3D_TOP_LERP))
|
||||
|| ((alpha_arg1 == WINED3DTA_TEXTURE) && alpha_op != WINED3D_TOP_SELECT_ARG2)
|
||||
|| ((alpha_arg2 == WINED3DTA_TEXTURE) && alpha_op != WINED3D_TOP_SELECT_ARG1)
|
||||
|| ((alpha_arg3 == WINED3DTA_TEXTURE)
|
||||
&& (alpha_op == WINED3D_TOP_MULTIPLY_ADD || alpha_op == WINED3D_TOP_LERP)))
|
||||
context->fixed_function_usage_map |= (1 << i);
|
||||
|
||||
if ((color_op == WINED3D_TOP_BUMPENVMAP || color_op == WINED3D_TOP_BUMPENVMAP_LUMINANCE)
|
||||
&& i < MAX_TEXTURES - 1)
|
||||
context->fixed_function_usage_map |= (1 << (i + 1));
|
||||
}
|
||||
}
|
||||
|
||||
static void context_map_fixed_function_samplers(struct wined3d_context *context,
|
||||
const struct wined3d_state *state)
|
||||
{
|
||||
unsigned int i, tex;
|
||||
WORD ffu_map;
|
||||
const struct wined3d_d3d_info *d3d_info = context->d3d_info;
|
||||
|
||||
context_update_fixed_function_usage_map(context, state);
|
||||
ffu_map = context->fixed_function_usage_map;
|
||||
|
||||
if (d3d_info->limits.ffp_textures == d3d_info->limits.ffp_blend_stages
|
||||
|| state->lowest_disabled_stage <= d3d_info->limits.ffp_textures)
|
||||
{
|
||||
for (i = 0; ffu_map; ffu_map >>= 1, ++i)
|
||||
{
|
||||
if (!(ffu_map & 1))
|
||||
continue;
|
||||
|
||||
if (context->tex_unit_map[i] != i)
|
||||
{
|
||||
context_map_stage(context, i, i);
|
||||
context_invalidate_state(context, STATE_SAMPLER(i));
|
||||
context_invalidate_texture_stage(context, i);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* Now work out the mapping */
|
||||
tex = 0;
|
||||
for (i = 0; ffu_map; ffu_map >>= 1, ++i)
|
||||
{
|
||||
if (!(ffu_map & 1))
|
||||
continue;
|
||||
|
||||
if (context->tex_unit_map[i] != tex)
|
||||
{
|
||||
context_map_stage(context, i, tex);
|
||||
context_invalidate_state(context, STATE_SAMPLER(i));
|
||||
context_invalidate_texture_stage(context, i);
|
||||
}
|
||||
|
||||
++tex;
|
||||
}
|
||||
}
|
||||
|
||||
static void context_map_psamplers(struct wined3d_context *context, const struct wined3d_state *state)
|
||||
{
|
||||
const enum wined3d_sampler_texture_type *sampler_type =
|
||||
state->pixel_shader->reg_maps.sampler_type;
|
||||
unsigned int i;
|
||||
const struct wined3d_d3d_info *d3d_info = context->d3d_info;
|
||||
|
||||
for (i = 0; i < MAX_FRAGMENT_SAMPLERS; ++i)
|
||||
{
|
||||
if (sampler_type[i] && context->tex_unit_map[i] != i)
|
||||
{
|
||||
context_map_stage(context, i, i);
|
||||
context_invalidate_state(context, STATE_SAMPLER(i));
|
||||
if (i < d3d_info->limits.ffp_blend_stages)
|
||||
context_invalidate_texture_stage(context, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static BOOL context_unit_free_for_vs(const struct wined3d_context *context,
|
||||
const enum wined3d_sampler_texture_type *pshader_sampler_tokens,
|
||||
const enum wined3d_sampler_texture_type *vshader_sampler_tokens, DWORD unit)
|
||||
{
|
||||
DWORD current_mapping = context->rev_tex_unit_map[unit];
|
||||
|
||||
/* Not currently used */
|
||||
if (current_mapping == WINED3D_UNMAPPED_STAGE)
|
||||
return TRUE;
|
||||
|
||||
if (current_mapping < MAX_FRAGMENT_SAMPLERS)
|
||||
{
|
||||
/* Used by a fragment sampler */
|
||||
|
||||
if (!pshader_sampler_tokens)
|
||||
{
|
||||
/* No pixel shader, check fixed function */
|
||||
return current_mapping >= MAX_TEXTURES || !(context->fixed_function_usage_map & (1 << current_mapping));
|
||||
}
|
||||
|
||||
/* Pixel shader, check the shader's sampler map */
|
||||
return !pshader_sampler_tokens[current_mapping];
|
||||
}
|
||||
|
||||
/* Used by a vertex sampler */
|
||||
return !vshader_sampler_tokens[current_mapping - MAX_FRAGMENT_SAMPLERS];
|
||||
}
|
||||
|
||||
static void context_map_vsamplers(struct wined3d_context *context, BOOL ps, const struct wined3d_state *state)
|
||||
{
|
||||
const enum wined3d_sampler_texture_type *vshader_sampler_type =
|
||||
state->vertex_shader->reg_maps.sampler_type;
|
||||
const enum wined3d_sampler_texture_type *pshader_sampler_type = NULL;
|
||||
const struct wined3d_gl_info *gl_info = context->gl_info;
|
||||
int start = min(MAX_COMBINED_SAMPLERS, gl_info->limits.combined_samplers) - 1;
|
||||
int i;
|
||||
|
||||
if (ps)
|
||||
{
|
||||
/* Note that we only care if a sampler is sampled or not, not the sampler's specific type.
|
||||
* Otherwise we'd need to call shader_update_samplers() here for 1.x pixelshaders. */
|
||||
pshader_sampler_type = state->pixel_shader->reg_maps.sampler_type;
|
||||
}
|
||||
|
||||
for (i = 0; i < MAX_VERTEX_SAMPLERS; ++i) {
|
||||
DWORD vsampler_idx = i + MAX_FRAGMENT_SAMPLERS;
|
||||
if (vshader_sampler_type[i])
|
||||
{
|
||||
if (context->tex_unit_map[vsampler_idx] != WINED3D_UNMAPPED_STAGE)
|
||||
{
|
||||
/* Already mapped somewhere */
|
||||
continue;
|
||||
}
|
||||
|
||||
while (start >= 0)
|
||||
{
|
||||
if (context_unit_free_for_vs(context, pshader_sampler_type, vshader_sampler_type, start))
|
||||
{
|
||||
context_map_stage(context, vsampler_idx, start);
|
||||
context_invalidate_state(context, STATE_SAMPLER(vsampler_idx));
|
||||
|
||||
--start;
|
||||
break;
|
||||
}
|
||||
|
||||
--start;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void context_update_tex_unit_map(struct wined3d_context *context, const struct wined3d_state *state)
|
||||
{
|
||||
BOOL vs = use_vs(state);
|
||||
BOOL ps = use_ps(state);
|
||||
/*
|
||||
* Rules are:
|
||||
* -> Pixel shaders need a 1:1 map. In theory the shader input could be mapped too, but
|
||||
* that would be really messy and require shader recompilation
|
||||
* -> When the mapping of a stage is changed, sampler and ALL texture stage states have
|
||||
* to be reset. Because of that try to work with a 1:1 mapping as much as possible
|
||||
*/
|
||||
if (ps)
|
||||
context_map_psamplers(context, state);
|
||||
else
|
||||
context_map_fixed_function_samplers(context, state);
|
||||
|
||||
if (vs)
|
||||
context_map_vsamplers(context, ps, state);
|
||||
}
|
||||
|
||||
/* Context activation is done by the caller. */
|
||||
void context_state_drawbuf(struct wined3d_context *context, const struct wined3d_state *state, DWORD state_id)
|
||||
{
|
||||
@ -2576,7 +2806,7 @@ BOOL context_apply_draw_state(struct wined3d_context *context, struct wined3d_de
|
||||
/* Preload resources before FBO setup. Texture preload in particular may
|
||||
* result in changes to the current FBO, due to using e.g. FBO blits for
|
||||
* updating a resource location. */
|
||||
device_update_tex_unit_map(device);
|
||||
context_update_tex_unit_map(context, state);
|
||||
device_preload_textures(device, context);
|
||||
if (isStateDirty(context, STATE_VDECL) || isStateDirty(context, STATE_STREAMSRC))
|
||||
context_update_stream_info(context, state);
|
||||
|
@ -171,7 +171,7 @@ void device_preload_textures(const struct wined3d_device *device, struct wined3d
|
||||
}
|
||||
else
|
||||
{
|
||||
WORD ffu_map = device->fixed_function_usage_map;
|
||||
WORD ffu_map = context->fixed_function_usage_map;
|
||||
|
||||
for (i = 0; ffu_map; ffu_map >>= 1, ++i)
|
||||
{
|
||||
@ -883,7 +883,6 @@ HRESULT CDECL wined3d_device_init_3d(struct wined3d_device *device,
|
||||
struct wined3d_context *context;
|
||||
DWORD clear_flags = 0;
|
||||
HRESULT hr;
|
||||
DWORD state;
|
||||
|
||||
TRACE("device %p, swapchain_desc %p.\n", device, swapchain_desc);
|
||||
|
||||
@ -895,21 +894,6 @@ HRESULT CDECL wined3d_device_init_3d(struct wined3d_device *device,
|
||||
device->fb.render_targets = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
|
||||
sizeof(*device->fb.render_targets) * gl_info->limits.buffers);
|
||||
|
||||
/* Initialize the texture unit mapping to a 1:1 mapping */
|
||||
for (state = 0; state < MAX_COMBINED_SAMPLERS; ++state)
|
||||
{
|
||||
if (state < gl_info->limits.fragment_samplers)
|
||||
{
|
||||
device->texUnitMap[state] = state;
|
||||
device->rev_tex_unit_map[state] = state;
|
||||
}
|
||||
else
|
||||
{
|
||||
device->texUnitMap[state] = WINED3D_UNMAPPED_STAGE;
|
||||
device->rev_tex_unit_map[state] = WINED3D_UNMAPPED_STAGE;
|
||||
}
|
||||
}
|
||||
|
||||
if (FAILED(hr = device->shader_backend->shader_alloc_private(device,
|
||||
device->adapter->vertex_pipe, device->adapter->fragment_pipe)))
|
||||
{
|
||||
@ -2442,217 +2426,6 @@ HRESULT CDECL wined3d_device_get_vs_consts_f(const struct wined3d_device *device
|
||||
return WINED3D_OK;
|
||||
}
|
||||
|
||||
static void device_invalidate_texture_stage(const struct wined3d_device *device, DWORD stage)
|
||||
{
|
||||
DWORD i;
|
||||
|
||||
for (i = 0; i <= WINED3D_HIGHEST_TEXTURE_STATE; ++i)
|
||||
{
|
||||
device_invalidate_state(device, STATE_TEXTURESTAGE(stage, i));
|
||||
}
|
||||
}
|
||||
|
||||
static void device_map_stage(struct wined3d_device *device, DWORD stage, DWORD unit)
|
||||
{
|
||||
DWORD i = device->rev_tex_unit_map[unit];
|
||||
DWORD j = device->texUnitMap[stage];
|
||||
|
||||
device->texUnitMap[stage] = unit;
|
||||
if (i != WINED3D_UNMAPPED_STAGE && i != stage)
|
||||
device->texUnitMap[i] = WINED3D_UNMAPPED_STAGE;
|
||||
|
||||
device->rev_tex_unit_map[unit] = stage;
|
||||
if (j != WINED3D_UNMAPPED_STAGE && j != unit)
|
||||
device->rev_tex_unit_map[j] = WINED3D_UNMAPPED_STAGE;
|
||||
}
|
||||
|
||||
static void device_update_fixed_function_usage_map(struct wined3d_device *device)
|
||||
{
|
||||
UINT i;
|
||||
|
||||
device->fixed_function_usage_map = 0;
|
||||
for (i = 0; i < MAX_TEXTURES; ++i)
|
||||
{
|
||||
const struct wined3d_state *state = &device->state;
|
||||
enum wined3d_texture_op color_op = state->texture_states[i][WINED3D_TSS_COLOR_OP];
|
||||
enum wined3d_texture_op alpha_op = state->texture_states[i][WINED3D_TSS_ALPHA_OP];
|
||||
DWORD color_arg1 = state->texture_states[i][WINED3D_TSS_COLOR_ARG1] & WINED3DTA_SELECTMASK;
|
||||
DWORD color_arg2 = state->texture_states[i][WINED3D_TSS_COLOR_ARG2] & WINED3DTA_SELECTMASK;
|
||||
DWORD color_arg3 = state->texture_states[i][WINED3D_TSS_COLOR_ARG0] & WINED3DTA_SELECTMASK;
|
||||
DWORD alpha_arg1 = state->texture_states[i][WINED3D_TSS_ALPHA_ARG1] & WINED3DTA_SELECTMASK;
|
||||
DWORD alpha_arg2 = state->texture_states[i][WINED3D_TSS_ALPHA_ARG2] & WINED3DTA_SELECTMASK;
|
||||
DWORD alpha_arg3 = state->texture_states[i][WINED3D_TSS_ALPHA_ARG0] & WINED3DTA_SELECTMASK;
|
||||
|
||||
/* Not used, and disable higher stages. */
|
||||
if (color_op == WINED3D_TOP_DISABLE)
|
||||
break;
|
||||
|
||||
if (((color_arg1 == WINED3DTA_TEXTURE) && color_op != WINED3D_TOP_SELECT_ARG2)
|
||||
|| ((color_arg2 == WINED3DTA_TEXTURE) && color_op != WINED3D_TOP_SELECT_ARG1)
|
||||
|| ((color_arg3 == WINED3DTA_TEXTURE)
|
||||
&& (color_op == WINED3D_TOP_MULTIPLY_ADD || color_op == WINED3D_TOP_LERP))
|
||||
|| ((alpha_arg1 == WINED3DTA_TEXTURE) && alpha_op != WINED3D_TOP_SELECT_ARG2)
|
||||
|| ((alpha_arg2 == WINED3DTA_TEXTURE) && alpha_op != WINED3D_TOP_SELECT_ARG1)
|
||||
|| ((alpha_arg3 == WINED3DTA_TEXTURE)
|
||||
&& (alpha_op == WINED3D_TOP_MULTIPLY_ADD || alpha_op == WINED3D_TOP_LERP)))
|
||||
device->fixed_function_usage_map |= (1 << i);
|
||||
|
||||
if ((color_op == WINED3D_TOP_BUMPENVMAP || color_op == WINED3D_TOP_BUMPENVMAP_LUMINANCE)
|
||||
&& i < MAX_TEXTURES - 1)
|
||||
device->fixed_function_usage_map |= (1 << (i + 1));
|
||||
}
|
||||
}
|
||||
|
||||
static void device_map_fixed_function_samplers(struct wined3d_device *device, const struct wined3d_d3d_info *d3d_info)
|
||||
{
|
||||
unsigned int i, tex;
|
||||
WORD ffu_map;
|
||||
|
||||
device_update_fixed_function_usage_map(device);
|
||||
ffu_map = device->fixed_function_usage_map;
|
||||
|
||||
if (d3d_info->limits.ffp_textures == d3d_info->limits.ffp_blend_stages
|
||||
|| device->state.lowest_disabled_stage <= d3d_info->limits.ffp_textures)
|
||||
{
|
||||
for (i = 0; ffu_map; ffu_map >>= 1, ++i)
|
||||
{
|
||||
if (!(ffu_map & 1)) continue;
|
||||
|
||||
if (device->texUnitMap[i] != i)
|
||||
{
|
||||
device_map_stage(device, i, i);
|
||||
device_invalidate_state(device, STATE_SAMPLER(i));
|
||||
device_invalidate_texture_stage(device, i);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* Now work out the mapping */
|
||||
tex = 0;
|
||||
for (i = 0; ffu_map; ffu_map >>= 1, ++i)
|
||||
{
|
||||
if (!(ffu_map & 1)) continue;
|
||||
|
||||
if (device->texUnitMap[i] != tex)
|
||||
{
|
||||
device_map_stage(device, i, tex);
|
||||
device_invalidate_state(device, STATE_SAMPLER(i));
|
||||
device_invalidate_texture_stage(device, i);
|
||||
}
|
||||
|
||||
++tex;
|
||||
}
|
||||
}
|
||||
|
||||
static void device_map_psamplers(struct wined3d_device *device, const struct wined3d_d3d_info *d3d_info)
|
||||
{
|
||||
const enum wined3d_sampler_texture_type *sampler_type =
|
||||
device->state.pixel_shader->reg_maps.sampler_type;
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < MAX_FRAGMENT_SAMPLERS; ++i)
|
||||
{
|
||||
if (sampler_type[i] && device->texUnitMap[i] != i)
|
||||
{
|
||||
device_map_stage(device, i, i);
|
||||
device_invalidate_state(device, STATE_SAMPLER(i));
|
||||
if (i < d3d_info->limits.ffp_blend_stages)
|
||||
device_invalidate_texture_stage(device, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static BOOL device_unit_free_for_vs(const struct wined3d_device *device,
|
||||
const enum wined3d_sampler_texture_type *pshader_sampler_tokens,
|
||||
const enum wined3d_sampler_texture_type *vshader_sampler_tokens, DWORD unit)
|
||||
{
|
||||
DWORD current_mapping = device->rev_tex_unit_map[unit];
|
||||
|
||||
/* Not currently used */
|
||||
if (current_mapping == WINED3D_UNMAPPED_STAGE) return TRUE;
|
||||
|
||||
if (current_mapping < MAX_FRAGMENT_SAMPLERS) {
|
||||
/* Used by a fragment sampler */
|
||||
|
||||
if (!pshader_sampler_tokens) {
|
||||
/* No pixel shader, check fixed function */
|
||||
return current_mapping >= MAX_TEXTURES || !(device->fixed_function_usage_map & (1 << current_mapping));
|
||||
}
|
||||
|
||||
/* Pixel shader, check the shader's sampler map */
|
||||
return !pshader_sampler_tokens[current_mapping];
|
||||
}
|
||||
|
||||
/* Used by a vertex sampler */
|
||||
return !vshader_sampler_tokens[current_mapping - MAX_FRAGMENT_SAMPLERS];
|
||||
}
|
||||
|
||||
static void device_map_vsamplers(struct wined3d_device *device, BOOL ps, const struct wined3d_gl_info *gl_info)
|
||||
{
|
||||
const enum wined3d_sampler_texture_type *vshader_sampler_type =
|
||||
device->state.vertex_shader->reg_maps.sampler_type;
|
||||
const enum wined3d_sampler_texture_type *pshader_sampler_type = NULL;
|
||||
int start = min(MAX_COMBINED_SAMPLERS, gl_info->limits.combined_samplers) - 1;
|
||||
int i;
|
||||
|
||||
if (ps)
|
||||
{
|
||||
/* Note that we only care if a sampler is sampled or not, not the sampler's specific type.
|
||||
* Otherwise we'd need to call shader_update_samplers() here for 1.x pixelshaders. */
|
||||
pshader_sampler_type = device->state.pixel_shader->reg_maps.sampler_type;
|
||||
}
|
||||
|
||||
for (i = 0; i < MAX_VERTEX_SAMPLERS; ++i) {
|
||||
DWORD vsampler_idx = i + MAX_FRAGMENT_SAMPLERS;
|
||||
if (vshader_sampler_type[i])
|
||||
{
|
||||
if (device->texUnitMap[vsampler_idx] != WINED3D_UNMAPPED_STAGE)
|
||||
{
|
||||
/* Already mapped somewhere */
|
||||
continue;
|
||||
}
|
||||
|
||||
while (start >= 0)
|
||||
{
|
||||
if (device_unit_free_for_vs(device, pshader_sampler_type, vshader_sampler_type, start))
|
||||
{
|
||||
device_map_stage(device, vsampler_idx, start);
|
||||
device_invalidate_state(device, STATE_SAMPLER(vsampler_idx));
|
||||
|
||||
--start;
|
||||
break;
|
||||
}
|
||||
|
||||
--start;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void device_update_tex_unit_map(struct wined3d_device *device)
|
||||
{
|
||||
const struct wined3d_gl_info *gl_info = &device->adapter->gl_info;
|
||||
const struct wined3d_d3d_info *d3d_info = &device->adapter->d3d_info;
|
||||
const struct wined3d_state *state = &device->state;
|
||||
BOOL vs = use_vs(state);
|
||||
BOOL ps = use_ps(state);
|
||||
/*
|
||||
* Rules are:
|
||||
* -> Pixel shaders need a 1:1 map. In theory the shader input could be mapped too, but
|
||||
* that would be really messy and require shader recompilation
|
||||
* -> When the mapping of a stage is changed, sampler and ALL texture stage states have
|
||||
* to be reset. Because of that try to work with a 1:1 mapping as much as possible
|
||||
*/
|
||||
if (ps)
|
||||
device_map_psamplers(device, d3d_info);
|
||||
else
|
||||
device_map_fixed_function_samplers(device, d3d_info);
|
||||
|
||||
if (vs)
|
||||
device_map_vsamplers(device, ps, gl_info);
|
||||
}
|
||||
|
||||
void CDECL wined3d_device_set_pixel_shader(struct wined3d_device *device, struct wined3d_shader *shader)
|
||||
{
|
||||
struct wined3d_shader *prev = device->update_state->pixel_shader;
|
||||
@ -4442,7 +4215,7 @@ HRESULT CDECL wined3d_device_set_cursor_properties(struct wined3d_device *device
|
||||
|
||||
context = context_acquire(device, NULL);
|
||||
|
||||
invalidate_active_texture(device, context);
|
||||
context_invalidate_active_texture(context);
|
||||
/* Create a new cursor texture */
|
||||
gl_info->gl_ops.gl.p_glGenTextures(1, &device->cursorTexture);
|
||||
checkGLcall("glGenTextures");
|
||||
|
@ -194,7 +194,7 @@ static void drawStridedSlow(const struct wined3d_device *device, struct wined3d_
|
||||
for (textureNo = 0; textureNo < texture_stages; ++textureNo)
|
||||
{
|
||||
int coordIdx = state->texture_states[textureNo][WINED3D_TSS_TEXCOORD_INDEX];
|
||||
DWORD texture_idx = device->texUnitMap[textureNo];
|
||||
DWORD texture_idx = context->tex_unit_map[textureNo];
|
||||
|
||||
if (!gl_info->supported[ARB_MULTITEXTURE] && textureNo > 0)
|
||||
{
|
||||
@ -266,7 +266,7 @@ static void drawStridedSlow(const struct wined3d_device *device, struct wined3d_
|
||||
coord_idx = state->texture_states[texture][WINED3D_TSS_TEXCOORD_INDEX];
|
||||
ptr = texCoords[coord_idx] + (SkipnStrides * si->elements[WINED3D_FFP_TEXCOORD0 + coord_idx].stride);
|
||||
|
||||
texture_idx = device->texUnitMap[texture];
|
||||
texture_idx = context->tex_unit_map[texture];
|
||||
ops->texcoord[si->elements[WINED3D_FFP_TEXCOORD0 + coord_idx].format->emit_idx](
|
||||
GL_TEXTURE0_ARB + texture_idx, ptr);
|
||||
}
|
||||
|
@ -5783,7 +5783,6 @@ static void set_glsl_shader_program(const struct wined3d_context *context, const
|
||||
GLhandleARB gs_id = 0;
|
||||
GLhandleARB ps_id = 0;
|
||||
struct list *ps_list, *vs_list;
|
||||
struct wined3d_device *device = context->swapchain->device;
|
||||
|
||||
if (!(context->shader_update_mask & (1 << WINED3D_SHADER_TYPE_VERTEX)))
|
||||
{
|
||||
@ -5985,8 +5984,8 @@ static void set_glsl_shader_program(const struct wined3d_context *context, const
|
||||
* fixed function fragment processing setups. So once the program is linked these samplers
|
||||
* won't change.
|
||||
*/
|
||||
shader_glsl_load_vsamplers(gl_info, device->texUnitMap, programId);
|
||||
shader_glsl_load_psamplers(gl_info, device->texUnitMap, programId);
|
||||
shader_glsl_load_vsamplers(gl_info, context->tex_unit_map, programId);
|
||||
shader_glsl_load_psamplers(gl_info, context->tex_unit_map, programId);
|
||||
|
||||
entry->constant_update_mask = 0;
|
||||
if (vshader)
|
||||
|
@ -477,9 +477,8 @@ void set_tex_op_nvrc(const struct wined3d_gl_info *gl_info, const struct wined3d
|
||||
static void nvrc_colorop(struct wined3d_context *context, const struct wined3d_state *state, DWORD state_id)
|
||||
{
|
||||
DWORD stage = (state_id - STATE_TEXTURESTAGE(0, 0)) / (WINED3D_HIGHEST_TEXTURE_STATE + 1);
|
||||
const struct wined3d_device *device = context->swapchain->device;
|
||||
BOOL tex_used = device->fixed_function_usage_map & (1 << stage);
|
||||
DWORD mapped_stage = device->texUnitMap[stage];
|
||||
BOOL tex_used = context->fixed_function_usage_map & (1 << stage);
|
||||
DWORD mapped_stage = context->tex_unit_map[stage];
|
||||
const struct wined3d_gl_info *gl_info = context->gl_info;
|
||||
|
||||
TRACE("Setting color op for stage %u.\n", stage);
|
||||
@ -584,7 +583,7 @@ static void nvrc_colorop(struct wined3d_context *context, const struct wined3d_s
|
||||
static void nvts_texdim(struct wined3d_context *context, const struct wined3d_state *state, DWORD state_id)
|
||||
{
|
||||
DWORD sampler = state_id - STATE_SAMPLER(0);
|
||||
DWORD mapped_stage = context->swapchain->device->texUnitMap[sampler];
|
||||
DWORD mapped_stage = context->tex_unit_map[sampler];
|
||||
|
||||
/* No need to enable / disable anything here for unused samplers. The tex_colorop
|
||||
* handler takes care. Also no action is needed with pixel shaders, or if tex_colorop
|
||||
@ -602,7 +601,7 @@ static void nvts_texdim(struct wined3d_context *context, const struct wined3d_st
|
||||
static void nvts_bumpenvmat(struct wined3d_context *context, const struct wined3d_state *state, DWORD state_id)
|
||||
{
|
||||
DWORD stage = (state_id - STATE_TEXTURESTAGE(0, 0)) / (WINED3D_HIGHEST_TEXTURE_STATE + 1);
|
||||
DWORD mapped_stage = context->swapchain->device->texUnitMap[stage + 1];
|
||||
DWORD mapped_stage = context->tex_unit_map[stage + 1];
|
||||
const struct wined3d_gl_info *gl_info = context->gl_info;
|
||||
float mat[2][2];
|
||||
|
||||
|
@ -3130,9 +3130,8 @@ static void set_tex_op(const struct wined3d_gl_info *gl_info, const struct wined
|
||||
static void tex_colorop(struct wined3d_context *context, const struct wined3d_state *state, DWORD state_id)
|
||||
{
|
||||
DWORD stage = (state_id - STATE_TEXTURESTAGE(0, 0)) / (WINED3D_HIGHEST_TEXTURE_STATE + 1);
|
||||
const struct wined3d_device *device = context->swapchain->device;
|
||||
BOOL tex_used = device->fixed_function_usage_map & (1 << stage);
|
||||
DWORD mapped_stage = device->texUnitMap[stage];
|
||||
BOOL tex_used = context->fixed_function_usage_map & (1 << stage);
|
||||
DWORD mapped_stage = context->tex_unit_map[stage];
|
||||
const struct wined3d_gl_info *gl_info = context->gl_info;
|
||||
|
||||
TRACE("Setting color op for stage %d\n", stage);
|
||||
@ -3192,9 +3191,8 @@ static void tex_colorop(struct wined3d_context *context, const struct wined3d_st
|
||||
void tex_alphaop(struct wined3d_context *context, const struct wined3d_state *state, DWORD state_id)
|
||||
{
|
||||
DWORD stage = (state_id - STATE_TEXTURESTAGE(0, 0)) / (WINED3D_HIGHEST_TEXTURE_STATE + 1);
|
||||
const struct wined3d_device *device = context->swapchain->device;
|
||||
BOOL tex_used = device->fixed_function_usage_map & (1 << stage);
|
||||
DWORD mapped_stage = device->texUnitMap[stage];
|
||||
BOOL tex_used = context->fixed_function_usage_map & (1 << stage);
|
||||
DWORD mapped_stage = context->tex_unit_map[stage];
|
||||
const struct wined3d_gl_info *gl_info = context->gl_info;
|
||||
DWORD op, arg1, arg2, arg0;
|
||||
|
||||
@ -3295,7 +3293,7 @@ void transform_texture(struct wined3d_context *context, const struct wined3d_sta
|
||||
DWORD texUnit = (state_id - STATE_TEXTURESTAGE(0, 0)) / (WINED3D_HIGHEST_TEXTURE_STATE + 1);
|
||||
const struct wined3d_device *device = context->swapchain->device;
|
||||
const struct wined3d_gl_info *gl_info = context->gl_info;
|
||||
DWORD mapped_stage = device->texUnitMap[texUnit];
|
||||
DWORD mapped_stage = context->tex_unit_map[texUnit];
|
||||
BOOL generated;
|
||||
int coordIdx;
|
||||
|
||||
@ -3351,7 +3349,6 @@ static void unload_tex_coords(const struct wined3d_gl_info *gl_info)
|
||||
static void load_tex_coords(const struct wined3d_context *context, const struct wined3d_stream_info *si,
|
||||
GLuint *curVBO, const struct wined3d_state *state)
|
||||
{
|
||||
const struct wined3d_device *device = context->swapchain->device;
|
||||
const struct wined3d_gl_info *gl_info = context->gl_info;
|
||||
unsigned int mapped_stage = 0;
|
||||
unsigned int textureNo = 0;
|
||||
@ -3360,7 +3357,7 @@ static void load_tex_coords(const struct wined3d_context *context, const struct
|
||||
{
|
||||
int coordIdx = state->texture_states[textureNo][WINED3D_TSS_TEXCOORD_INDEX];
|
||||
|
||||
mapped_stage = device->texUnitMap[textureNo];
|
||||
mapped_stage = context->tex_unit_map[textureNo];
|
||||
if (mapped_stage == WINED3D_UNMAPPED_STAGE) continue;
|
||||
|
||||
if (mapped_stage >= gl_info->limits.texture_coords)
|
||||
@ -3411,13 +3408,12 @@ static void load_tex_coords(const struct wined3d_context *context, const struct
|
||||
static void tex_coordindex(struct wined3d_context *context, const struct wined3d_state *state, DWORD state_id)
|
||||
{
|
||||
DWORD stage = (state_id - STATE_TEXTURESTAGE(0, 0)) / (WINED3D_HIGHEST_TEXTURE_STATE + 1);
|
||||
const struct wined3d_device *device = context->swapchain->device;
|
||||
static const GLfloat s_plane[] = { 1.0f, 0.0f, 0.0f, 0.0f };
|
||||
static const GLfloat t_plane[] = { 0.0f, 1.0f, 0.0f, 0.0f };
|
||||
static const GLfloat r_plane[] = { 0.0f, 0.0f, 1.0f, 0.0f };
|
||||
static const GLfloat q_plane[] = { 0.0f, 0.0f, 0.0f, 1.0f };
|
||||
const struct wined3d_gl_info *gl_info = context->gl_info;
|
||||
DWORD mapped_stage = device->texUnitMap[stage];
|
||||
DWORD mapped_stage = context->tex_unit_map[stage];
|
||||
|
||||
if (mapped_stage == WINED3D_UNMAPPED_STAGE)
|
||||
{
|
||||
@ -3605,24 +3601,21 @@ void sampler_texmatrix(struct wined3d_context *context, const struct wined3d_sta
|
||||
|
||||
if (texIsPow2 || (context->lastWasPow2Texture & (1 << sampler)))
|
||||
{
|
||||
const struct wined3d_device *device = context->swapchain->device;
|
||||
|
||||
if (texIsPow2)
|
||||
context->lastWasPow2Texture |= 1 << sampler;
|
||||
else
|
||||
context->lastWasPow2Texture &= ~(1 << sampler);
|
||||
|
||||
transform_texture(context, state,
|
||||
STATE_TEXTURESTAGE(device->texUnitMap[sampler], WINED3D_TSS_TEXTURE_TRANSFORM_FLAGS));
|
||||
STATE_TEXTURESTAGE(context->tex_unit_map[sampler], WINED3D_TSS_TEXTURE_TRANSFORM_FLAGS));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void sampler(struct wined3d_context *context, const struct wined3d_state *state, DWORD state_id)
|
||||
{
|
||||
const struct wined3d_device *device = context->swapchain->device;
|
||||
DWORD sampler = state_id - STATE_SAMPLER(0);
|
||||
DWORD mapped_stage = device->texUnitMap[sampler];
|
||||
DWORD mapped_stage = context->tex_unit_map[sampler];
|
||||
const struct wined3d_gl_info *gl_info = context->gl_info;
|
||||
union {
|
||||
float f;
|
||||
|
@ -654,7 +654,6 @@ static void surface_bind(struct wined3d_surface *surface, struct wined3d_context
|
||||
static void surface_bind_and_dirtify(struct wined3d_surface *surface,
|
||||
struct wined3d_context *context, BOOL srgb)
|
||||
{
|
||||
struct wined3d_device *device = surface->resource.device;
|
||||
DWORD active_sampler;
|
||||
|
||||
/* We don't need a specific texture unit, but after binding the texture
|
||||
@ -666,10 +665,10 @@ static void surface_bind_and_dirtify(struct wined3d_surface *surface,
|
||||
* called from sampler() in state.c. This means we can't touch anything
|
||||
* other than whatever happens to be the currently active texture, or we
|
||||
* would risk marking already applied sampler states dirty again. */
|
||||
active_sampler = device->rev_tex_unit_map[context->active_texture];
|
||||
active_sampler = context->rev_tex_unit_map[context->active_texture];
|
||||
|
||||
if (active_sampler != WINED3D_UNMAPPED_STAGE)
|
||||
device_invalidate_state(device, STATE_SAMPLER(active_sampler));
|
||||
context_invalidate_state(context, STATE_SAMPLER(active_sampler));
|
||||
surface_bind(surface, context, srgb);
|
||||
}
|
||||
|
||||
@ -2247,7 +2246,7 @@ HRESULT surface_upload_from_surface(struct wined3d_surface *dst_surface, const P
|
||||
|
||||
surface_upload_data(dst_surface, gl_info, src_format, src_rect, src_pitch, dst_point, FALSE, &data);
|
||||
|
||||
invalidate_active_texture(dst_surface->resource.device, context);
|
||||
context_invalidate_active_texture(context);
|
||||
|
||||
context_release(context);
|
||||
|
||||
|
@ -3497,7 +3497,7 @@ void texture_activate_dimensions(const struct wined3d_texture *texture, const st
|
||||
void sampler_texdim(struct wined3d_context *context, const struct wined3d_state *state, DWORD state_id)
|
||||
{
|
||||
DWORD sampler = state_id - STATE_SAMPLER(0);
|
||||
DWORD mapped_stage = context->swapchain->device->texUnitMap[sampler];
|
||||
DWORD mapped_stage = context->tex_unit_map[sampler];
|
||||
|
||||
/* No need to enable / disable anything here for unused samplers. The
|
||||
* tex_colorop handler takes care. Also no action is needed with pixel
|
||||
|
@ -42,10 +42,10 @@ static void volume_bind_and_dirtify(const struct wined3d_volume *volume,
|
||||
* from sampler() in state.c. This means we can't touch anything other than
|
||||
* whatever happens to be the currently active texture, or we would risk
|
||||
* marking already applied sampler states dirty again. */
|
||||
active_sampler = volume->resource.device->rev_tex_unit_map[context->active_texture];
|
||||
active_sampler = context->rev_tex_unit_map[context->active_texture];
|
||||
|
||||
if (active_sampler != WINED3D_UNMAPPED_STAGE)
|
||||
device_invalidate_state(volume->resource.device, STATE_SAMPLER(active_sampler));
|
||||
context_invalidate_state(context, STATE_SAMPLER(active_sampler));
|
||||
|
||||
container->texture_ops->texture_bind(container, context, srgb);
|
||||
}
|
||||
|
@ -1086,6 +1086,8 @@ struct wined3d_context
|
||||
DWORD use_immediate_mode_draw : 1;
|
||||
DWORD texShaderBumpMap : 8; /* MAX_TEXTURES, 8 */
|
||||
DWORD lastWasPow2Texture : 8; /* MAX_TEXTURES, 8 */
|
||||
DWORD fixed_function_usage_map : 8; /* MAX_TEXTURES, 8 */
|
||||
DWORD padding : 24;
|
||||
DWORD shader_update_mask;
|
||||
DWORD constant_update_mask;
|
||||
DWORD numbered_array_mask;
|
||||
@ -1138,6 +1140,9 @@ struct wined3d_context
|
||||
struct wined3d_event_query *buffer_queries[MAX_ATTRIBS];
|
||||
unsigned int num_buffer_queries;
|
||||
|
||||
DWORD tex_unit_map[MAX_COMBINED_SAMPLERS];
|
||||
DWORD rev_tex_unit_map[MAX_COMBINED_SAMPLERS];
|
||||
|
||||
/* Extension emulation */
|
||||
GLint gl_fog_source;
|
||||
GLfloat fog_coord_value;
|
||||
@ -1882,18 +1887,18 @@ struct wined3d_device
|
||||
|
||||
UINT instance_count;
|
||||
|
||||
WORD vertexBlendUsed : 1; /* To avoid needless setting of the blend matrices */
|
||||
WORD bCursorVisible : 1;
|
||||
WORD d3d_initialized : 1;
|
||||
WORD inScene : 1; /* A flag to check for proper BeginScene / EndScene call pairs */
|
||||
WORD softwareVertexProcessing : 1; /* process vertex shaders using software or hardware */
|
||||
WORD filter_messages : 1;
|
||||
WORD padding : 10;
|
||||
|
||||
BYTE fixed_function_usage_map; /* MAX_TEXTURES, 8 */
|
||||
BYTE vertexBlendUsed : 1; /* To avoid needless setting of the blend matrices */
|
||||
BYTE bCursorVisible : 1;
|
||||
BYTE d3d_initialized : 1;
|
||||
BYTE inScene : 1; /* A flag to check for proper BeginScene / EndScene call pairs */
|
||||
BYTE softwareVertexProcessing : 1; /* process vertex shaders using software or hardware */
|
||||
BYTE filter_messages : 1;
|
||||
BYTE padding : 2;
|
||||
|
||||
unsigned char surface_alignment; /* Line Alignment of surfaces */
|
||||
|
||||
WORD padding2 : 16;
|
||||
|
||||
struct wined3d_state state;
|
||||
struct wined3d_state *update_state;
|
||||
struct wined3d_stateblock *recording;
|
||||
@ -1934,10 +1939,6 @@ struct wined3d_device
|
||||
UINT dummy_texture_3d[MAX_COMBINED_SAMPLERS];
|
||||
UINT dummy_texture_cube[MAX_COMBINED_SAMPLERS];
|
||||
|
||||
/* With register combiners we can skip junk texture stages */
|
||||
DWORD texUnitMap[MAX_COMBINED_SAMPLERS];
|
||||
DWORD rev_tex_unit_map[MAX_COMBINED_SAMPLERS];
|
||||
|
||||
/* Context management */
|
||||
struct wined3d_context **contexts;
|
||||
UINT context_count;
|
||||
@ -1958,7 +1959,6 @@ void device_resource_add(struct wined3d_device *device, struct wined3d_resource
|
||||
void device_resource_released(struct wined3d_device *device, struct wined3d_resource *resource) DECLSPEC_HIDDEN;
|
||||
void device_switch_onscreen_ds(struct wined3d_device *device, struct wined3d_context *context,
|
||||
struct wined3d_surface *depth_stencil) DECLSPEC_HIDDEN;
|
||||
void device_update_tex_unit_map(struct wined3d_device *device) DECLSPEC_HIDDEN;
|
||||
void device_invalidate_state(const struct wined3d_device *device, DWORD state) DECLSPEC_HIDDEN;
|
||||
|
||||
static inline BOOL isStateDirty(const struct wined3d_context *context, DWORD state)
|
||||
@ -1968,9 +1968,9 @@ static inline BOOL isStateDirty(const struct wined3d_context *context, DWORD sta
|
||||
return context->isStateDirty[idx] & (1 << shift);
|
||||
}
|
||||
|
||||
static inline void invalidate_active_texture(const struct wined3d_device *device, struct wined3d_context *context)
|
||||
static inline void context_invalidate_active_texture(struct wined3d_context *context)
|
||||
{
|
||||
DWORD sampler = device->rev_tex_unit_map[context->active_texture];
|
||||
DWORD sampler = context->rev_tex_unit_map[context->active_texture];
|
||||
if (sampler != WINED3D_UNMAPPED_STAGE)
|
||||
context_invalidate_state(context, STATE_SAMPLER(sampler));
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user