gallium: implement modern sampling scheme
largely a merge of the previously discussed origin/gallium-resource-sampling but updated. the idea is to allow arbitrary binding of resources, the way opencl, new gl versions and dx10+ require, i.e. DCL RES[0], 2D, FLOAT LOAD DST[0], SRC[0], RES[0] SAMPLE DST[0], SRC[0], RES[0], SAMP[0]
This commit is contained in:
@@ -26,6 +26,7 @@
|
||||
**************************************************************************/
|
||||
|
||||
#include "util/u_debug.h"
|
||||
#include "pipe/p_format.h"
|
||||
#include "pipe/p_shader_tokens.h"
|
||||
#include "tgsi_build.h"
|
||||
#include "tgsi_parse.h"
|
||||
@@ -226,6 +227,45 @@ tgsi_build_declaration_semantic(
|
||||
return ds;
|
||||
}
|
||||
|
||||
|
||||
static struct tgsi_declaration_resource
|
||||
tgsi_default_declaration_resource(void)
|
||||
{
|
||||
struct tgsi_declaration_resource declaration_resource;
|
||||
|
||||
declaration_resource.Resource = TGSI_TEXTURE_UNKNOWN;
|
||||
declaration_resource.ReturnTypeX = PIPE_TYPE_UNORM;
|
||||
declaration_resource.ReturnTypeY = PIPE_TYPE_UNORM;
|
||||
declaration_resource.ReturnTypeZ = PIPE_TYPE_UNORM;
|
||||
declaration_resource.ReturnTypeW = PIPE_TYPE_UNORM;
|
||||
|
||||
return declaration_resource;
|
||||
}
|
||||
|
||||
static struct tgsi_declaration_resource
|
||||
tgsi_build_declaration_resource(unsigned texture,
|
||||
unsigned return_type_x,
|
||||
unsigned return_type_y,
|
||||
unsigned return_type_z,
|
||||
unsigned return_type_w,
|
||||
struct tgsi_declaration *declaration,
|
||||
struct tgsi_header *header)
|
||||
{
|
||||
struct tgsi_declaration_resource declaration_resource;
|
||||
|
||||
declaration_resource = tgsi_default_declaration_resource();
|
||||
declaration_resource.Resource = texture;
|
||||
declaration_resource.ReturnTypeX = return_type_x;
|
||||
declaration_resource.ReturnTypeY = return_type_y;
|
||||
declaration_resource.ReturnTypeZ = return_type_z;
|
||||
declaration_resource.ReturnTypeW = return_type_w;
|
||||
|
||||
declaration_grow(declaration, header);
|
||||
|
||||
return declaration_resource;
|
||||
}
|
||||
|
||||
|
||||
struct tgsi_full_declaration
|
||||
tgsi_default_full_declaration( void )
|
||||
{
|
||||
@@ -235,6 +275,7 @@ tgsi_default_full_declaration( void )
|
||||
full_declaration.Range = tgsi_default_declaration_range();
|
||||
full_declaration.Semantic = tgsi_default_declaration_semantic();
|
||||
full_declaration.ImmediateData.u = NULL;
|
||||
full_declaration.Resource = tgsi_default_declaration_resource();
|
||||
|
||||
return full_declaration;
|
||||
}
|
||||
@@ -324,6 +365,24 @@ tgsi_build_full_declaration(
|
||||
}
|
||||
}
|
||||
|
||||
if (full_decl->Declaration.File == TGSI_FILE_RESOURCE) {
|
||||
struct tgsi_declaration_resource *dr;
|
||||
|
||||
if (maxsize <= size) {
|
||||
return 0;
|
||||
}
|
||||
dr = (struct tgsi_declaration_resource *)&tokens[size];
|
||||
size++;
|
||||
|
||||
*dr = tgsi_build_declaration_resource(full_decl->Resource.Resource,
|
||||
full_decl->Resource.ReturnTypeX,
|
||||
full_decl->Resource.ReturnTypeY,
|
||||
full_decl->Resource.ReturnTypeZ,
|
||||
full_decl->Resource.ReturnTypeW,
|
||||
declaration,
|
||||
header);
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
|
@@ -104,7 +104,8 @@ tgsi_file_names[TGSI_FILE_COUNT] =
|
||||
"PRED",
|
||||
"SV",
|
||||
"IMMX",
|
||||
"TEMPX"
|
||||
"TEMPX",
|
||||
"RES"
|
||||
};
|
||||
|
||||
static const char *interpolate_names[] =
|
||||
@@ -170,6 +171,15 @@ const char *tgsi_property_names[TGSI_PROPERTY_COUNT] =
|
||||
"FS_COLOR0_WRITES_ALL_CBUFS",
|
||||
};
|
||||
|
||||
static const char *tgsi_type_names[] =
|
||||
{
|
||||
"UNORM",
|
||||
"SNORM",
|
||||
"SINT",
|
||||
"UINT",
|
||||
"FLOAT"
|
||||
};
|
||||
|
||||
const char *tgsi_primitive_names[PIPE_PRIM_MAX] =
|
||||
{
|
||||
"POINTS",
|
||||
@@ -393,6 +403,26 @@ iter_declaration(
|
||||
}
|
||||
}
|
||||
|
||||
if (decl->Declaration.File == TGSI_FILE_RESOURCE) {
|
||||
TXT(", ");
|
||||
ENM(decl->Resource.Resource, tgsi_texture_names);
|
||||
TXT(", ");
|
||||
if ((decl->Resource.ReturnTypeX == decl->Resource.ReturnTypeY) &&
|
||||
(decl->Resource.ReturnTypeX == decl->Resource.ReturnTypeZ) &&
|
||||
(decl->Resource.ReturnTypeX == decl->Resource.ReturnTypeW)) {
|
||||
ENM(decl->Resource.ReturnTypeX, tgsi_type_names);
|
||||
} else {
|
||||
ENM(decl->Resource.ReturnTypeX, tgsi_type_names);
|
||||
TXT(", ");
|
||||
ENM(decl->Resource.ReturnTypeY, tgsi_type_names);
|
||||
TXT(", ");
|
||||
ENM(decl->Resource.ReturnTypeZ, tgsi_type_names);
|
||||
TXT(", ");
|
||||
ENM(decl->Resource.ReturnTypeW, tgsi_type_names);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (iter->processor.Processor == TGSI_PROCESSOR_FRAGMENT &&
|
||||
decl->Declaration.File == TGSI_FILE_INPUT)
|
||||
{
|
||||
|
@@ -1843,6 +1843,164 @@ exec_txd(struct tgsi_exec_machine *mach,
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void
|
||||
exec_sample(struct tgsi_exec_machine *mach,
|
||||
const struct tgsi_full_instruction *inst,
|
||||
uint modifier)
|
||||
{
|
||||
const uint resource_unit = inst->Src[1].Register.Index;
|
||||
const uint sampler_unit = inst->Src[2].Register.Index;
|
||||
union tgsi_exec_channel r[4];
|
||||
const union tgsi_exec_channel *lod = &ZeroVec;
|
||||
enum tgsi_sampler_control control;
|
||||
uint chan;
|
||||
|
||||
if (modifier != TEX_MODIFIER_NONE) {
|
||||
if (modifier == TEX_MODIFIER_LOD_BIAS)
|
||||
FETCH(&r[3], 3, CHAN_X);
|
||||
else /*TEX_MODIFIER_LOD*/
|
||||
FETCH(&r[3], 0, CHAN_W);
|
||||
|
||||
if (modifier != TEX_MODIFIER_PROJECTED) {
|
||||
lod = &r[3];
|
||||
}
|
||||
}
|
||||
|
||||
if (modifier == TEX_MODIFIER_EXPLICIT_LOD) {
|
||||
control = tgsi_sampler_lod_explicit;
|
||||
} else {
|
||||
control = tgsi_sampler_lod_bias;
|
||||
}
|
||||
|
||||
switch (mach->Resources[resource_unit].Resource) {
|
||||
case TGSI_TEXTURE_1D:
|
||||
case TGSI_TEXTURE_SHADOW1D:
|
||||
FETCH(&r[0], 0, CHAN_X);
|
||||
|
||||
if (modifier == TEX_MODIFIER_PROJECTED) {
|
||||
micro_div(&r[0], &r[0], &r[3]);
|
||||
}
|
||||
|
||||
fetch_texel(mach->Samplers[sampler_unit],
|
||||
&r[0], &ZeroVec, &ZeroVec, lod, /* S, T, P, LOD */
|
||||
control,
|
||||
&r[0], &r[1], &r[2], &r[3]); /* R, G, B, A */
|
||||
break;
|
||||
|
||||
case TGSI_TEXTURE_2D:
|
||||
case TGSI_TEXTURE_RECT:
|
||||
case TGSI_TEXTURE_SHADOW2D:
|
||||
case TGSI_TEXTURE_SHADOWRECT:
|
||||
FETCH(&r[0], 0, CHAN_X);
|
||||
FETCH(&r[1], 0, CHAN_Y);
|
||||
FETCH(&r[2], 0, CHAN_Z);
|
||||
|
||||
if (modifier == TEX_MODIFIER_PROJECTED) {
|
||||
micro_div(&r[0], &r[0], &r[3]);
|
||||
micro_div(&r[1], &r[1], &r[3]);
|
||||
micro_div(&r[2], &r[2], &r[3]);
|
||||
}
|
||||
|
||||
fetch_texel(mach->Samplers[sampler_unit],
|
||||
&r[0], &r[1], &r[2], lod, /* S, T, P, LOD */
|
||||
control,
|
||||
&r[0], &r[1], &r[2], &r[3]); /* outputs */
|
||||
break;
|
||||
|
||||
case TGSI_TEXTURE_3D:
|
||||
case TGSI_TEXTURE_CUBE:
|
||||
FETCH(&r[0], 0, CHAN_X);
|
||||
FETCH(&r[1], 0, CHAN_Y);
|
||||
FETCH(&r[2], 0, CHAN_Z);
|
||||
|
||||
if (modifier == TEX_MODIFIER_PROJECTED) {
|
||||
micro_div(&r[0], &r[0], &r[3]);
|
||||
micro_div(&r[1], &r[1], &r[3]);
|
||||
micro_div(&r[2], &r[2], &r[3]);
|
||||
}
|
||||
|
||||
fetch_texel(mach->Samplers[sampler_unit],
|
||||
&r[0], &r[1], &r[2], lod,
|
||||
control,
|
||||
&r[0], &r[1], &r[2], &r[3]);
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
|
||||
for (chan = 0; chan < NUM_CHANNELS; chan++) {
|
||||
if (inst->Dst[0].Register.WriteMask & (1 << chan)) {
|
||||
store_dest(mach, &r[chan], &inst->Dst[0], inst, chan, TGSI_EXEC_DATA_FLOAT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
exec_sample_d(struct tgsi_exec_machine *mach,
|
||||
const struct tgsi_full_instruction *inst)
|
||||
{
|
||||
const uint resource_unit = inst->Src[1].Register.Index;
|
||||
const uint sampler_unit = inst->Src[2].Register.Index;
|
||||
union tgsi_exec_channel r[4];
|
||||
uint chan;
|
||||
/*
|
||||
* XXX: This is fake SAMPLE_D -- the derivatives are not taken into account, yet.
|
||||
*/
|
||||
|
||||
switch (mach->Resources[resource_unit].Resource) {
|
||||
case TGSI_TEXTURE_1D:
|
||||
case TGSI_TEXTURE_SHADOW1D:
|
||||
|
||||
FETCH(&r[0], 0, CHAN_X);
|
||||
|
||||
fetch_texel(mach->Samplers[sampler_unit],
|
||||
&r[0], &ZeroVec, &ZeroVec, &ZeroVec, /* S, T, P, BIAS */
|
||||
tgsi_sampler_lod_bias,
|
||||
&r[0], &r[1], &r[2], &r[3]); /* R, G, B, A */
|
||||
break;
|
||||
|
||||
case TGSI_TEXTURE_2D:
|
||||
case TGSI_TEXTURE_RECT:
|
||||
case TGSI_TEXTURE_SHADOW2D:
|
||||
case TGSI_TEXTURE_SHADOWRECT:
|
||||
|
||||
FETCH(&r[0], 0, CHAN_X);
|
||||
FETCH(&r[1], 0, CHAN_Y);
|
||||
FETCH(&r[2], 0, CHAN_Z);
|
||||
|
||||
fetch_texel(mach->Samplers[sampler_unit],
|
||||
&r[0], &r[1], &r[2], &ZeroVec, /* inputs */
|
||||
tgsi_sampler_lod_bias,
|
||||
&r[0], &r[1], &r[2], &r[3]); /* outputs */
|
||||
break;
|
||||
|
||||
case TGSI_TEXTURE_3D:
|
||||
case TGSI_TEXTURE_CUBE:
|
||||
|
||||
FETCH(&r[0], 0, CHAN_X);
|
||||
FETCH(&r[1], 0, CHAN_Y);
|
||||
FETCH(&r[2], 0, CHAN_Z);
|
||||
|
||||
fetch_texel(mach->Samplers[sampler_unit],
|
||||
&r[0], &r[1], &r[2], &ZeroVec,
|
||||
tgsi_sampler_lod_bias,
|
||||
&r[0], &r[1], &r[2], &r[3]);
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
|
||||
for (chan = 0; chan < NUM_CHANNELS; chan++) {
|
||||
if (inst->Dst[0].Register.WriteMask & (1 << chan)) {
|
||||
store_dest(mach, &r[chan], &inst->Dst[0], inst, chan, TGSI_EXEC_DATA_FLOAT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Evaluate a constant-valued coefficient at the position of the
|
||||
* current quad.
|
||||
@@ -1914,6 +2072,11 @@ static void
|
||||
exec_declaration(struct tgsi_exec_machine *mach,
|
||||
const struct tgsi_full_declaration *decl)
|
||||
{
|
||||
if (decl->Declaration.File == TGSI_FILE_RESOURCE) {
|
||||
mach->Resources[decl->Range.First] = decl->Resource;
|
||||
return;
|
||||
}
|
||||
|
||||
if (mach->Processor == TGSI_PROCESSOR_FRAGMENT) {
|
||||
if (decl->Declaration.File == TGSI_FILE_INPUT) {
|
||||
uint first, last, mask;
|
||||
@@ -3688,6 +3851,54 @@ exec_instruction(
|
||||
exec_endswitch(mach);
|
||||
break;
|
||||
|
||||
case TGSI_OPCODE_LOAD:
|
||||
assert(0);
|
||||
break;
|
||||
|
||||
case TGSI_OPCODE_LOAD_MS:
|
||||
assert(0);
|
||||
break;
|
||||
|
||||
case TGSI_OPCODE_SAMPLE:
|
||||
exec_sample(mach, inst, TEX_MODIFIER_NONE);
|
||||
break;
|
||||
|
||||
case TGSI_OPCODE_SAMPLE_B:
|
||||
exec_sample(mach, inst, TEX_MODIFIER_LOD_BIAS);
|
||||
break;
|
||||
|
||||
case TGSI_OPCODE_SAMPLE_C:
|
||||
exec_sample(mach, inst, TEX_MODIFIER_NONE);
|
||||
break;
|
||||
|
||||
case TGSI_OPCODE_SAMPLE_C_LZ:
|
||||
exec_sample(mach, inst, TEX_MODIFIER_LOD_BIAS);
|
||||
break;
|
||||
|
||||
case TGSI_OPCODE_SAMPLE_D:
|
||||
exec_sample_d(mach, inst);
|
||||
break;
|
||||
|
||||
case TGSI_OPCODE_SAMPLE_L:
|
||||
exec_sample(mach, inst, TEX_MODIFIER_EXPLICIT_LOD);
|
||||
break;
|
||||
|
||||
case TGSI_OPCODE_GATHER4:
|
||||
assert(0);
|
||||
break;
|
||||
|
||||
case TGSI_OPCODE_RESINFO:
|
||||
assert(0);
|
||||
break;
|
||||
|
||||
case TGSI_OPCODE_SAMPLE_POS:
|
||||
assert(0);
|
||||
break;
|
||||
|
||||
case TGSI_OPCODE_SAMPLE_INFO:
|
||||
assert(0);
|
||||
break;
|
||||
|
||||
default:
|
||||
assert( 0 );
|
||||
}
|
||||
|
@@ -308,6 +308,7 @@ struct tgsi_exec_machine
|
||||
struct tgsi_full_declaration *Declarations;
|
||||
uint NumDeclarations;
|
||||
|
||||
struct tgsi_declaration_resource Resources[PIPE_MAX_SHADER_RESOURCES];
|
||||
};
|
||||
|
||||
struct tgsi_exec_machine *
|
||||
|
@@ -175,7 +175,20 @@ static const struct tgsi_opcode_info opcode_info[TGSI_OPCODE_LAST] =
|
||||
{ 0, 1, 0, 0, 0, 0, "SWITCH", TGSI_OPCODE_SWITCH },
|
||||
{ 0, 1, 0, 0, 0, 0, "CASE", TGSI_OPCODE_CASE },
|
||||
{ 0, 0, 0, 0, 0, 0, "DEFAULT", TGSI_OPCODE_DEFAULT },
|
||||
{ 0, 0, 0, 0, 0, 0, "ENDSWITCH", TGSI_OPCODE_ENDSWITCH }
|
||||
{ 0, 0, 0, 0, 0, 0, "ENDSWITCH", TGSI_OPCODE_ENDSWITCH },
|
||||
|
||||
{ 1, 2, 0, 0, 0, 0, "LOAD", TGSI_OPCODE_LOAD },
|
||||
{ 1, 2, 0, 0, 0, 0, "LOAD_MS", TGSI_OPCODE_LOAD_MS },
|
||||
{ 1, 3, 0, 0, 0, 0, "SAMPLE", TGSI_OPCODE_SAMPLE },
|
||||
{ 1, 4, 0, 0, 0, 0, "SAMPLE_B", TGSI_OPCODE_SAMPLE_B },
|
||||
{ 1, 4, 0, 0, 0, 0, "SAMPLE_C", TGSI_OPCODE_SAMPLE_C },
|
||||
{ 1, 4, 0, 0, 0, 0, "SAMPLE_C_LZ", TGSI_OPCODE_SAMPLE_C_LZ },
|
||||
{ 1, 5, 0, 0, 0, 0, "SAMPLE_D", TGSI_OPCODE_SAMPLE_D },
|
||||
{ 1, 3, 0, 0, 0, 0, "SAMPLE_L", TGSI_OPCODE_SAMPLE_L },
|
||||
{ 1, 3, 0, 0, 0, 0, "GATHER4", TGSI_OPCODE_GATHER4 },
|
||||
{ 1, 2, 0, 0, 0, 0, "RESINFO", TGSI_OPCODE_RESINFO },
|
||||
{ 1, 2, 0, 0, 0, 0, "SAMPLE_POS", TGSI_OPCODE_SAMPLE_POS },
|
||||
{ 1, 2, 0, 0, 0, 0, "SAMPLE_INFO", TGSI_OPCODE_SAMPLE_INFO },
|
||||
};
|
||||
|
||||
const struct tgsi_opcode_info *
|
||||
|
@@ -168,6 +168,19 @@ OP01(CASE)
|
||||
OP00(DEFAULT)
|
||||
OP00(ENDSWITCH)
|
||||
|
||||
OP12(LOAD)
|
||||
OP12(LOAD_MS)
|
||||
OP13(SAMPLE)
|
||||
OP14(SAMPLE_B)
|
||||
OP14(SAMPLE_C)
|
||||
OP14(SAMPLE_C_LZ)
|
||||
OP15(SAMPLE_D)
|
||||
OP13(SAMPLE_L)
|
||||
OP13(GATHER4)
|
||||
OP12(RESINFO)
|
||||
OP13(SAMPLE_POS)
|
||||
OP12(SAMPLE_INFO)
|
||||
|
||||
|
||||
#undef OP00
|
||||
#undef OP01
|
||||
@@ -180,6 +193,10 @@ OP00(ENDSWITCH)
|
||||
#undef OP14
|
||||
#endif
|
||||
|
||||
#ifdef OP15
|
||||
#undef OP15
|
||||
#endif
|
||||
|
||||
#undef OP00_LBL
|
||||
#undef OP01_LBL
|
||||
|
||||
|
@@ -128,6 +128,10 @@ tgsi_parse_token(
|
||||
}
|
||||
}
|
||||
|
||||
if (decl->Declaration.File == TGSI_FILE_RESOURCE) {
|
||||
next_token(ctx, &decl->Resource);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
@@ -69,6 +69,7 @@ struct tgsi_full_declaration
|
||||
struct tgsi_declaration_dimension Dim;
|
||||
struct tgsi_declaration_semantic Semantic;
|
||||
struct tgsi_immediate_array_data ImmediateData;
|
||||
struct tgsi_declaration_resource Resource;
|
||||
};
|
||||
|
||||
struct tgsi_full_immediate
|
||||
@@ -84,7 +85,7 @@ struct tgsi_full_property
|
||||
};
|
||||
|
||||
#define TGSI_FULL_MAX_DST_REGISTERS 2
|
||||
#define TGSI_FULL_MAX_SRC_REGISTERS 4 /* TXD has 4 */
|
||||
#define TGSI_FULL_MAX_SRC_REGISTERS 5 /* SAMPLE_D has 5 */
|
||||
|
||||
struct tgsi_full_instruction
|
||||
{
|
||||
|
@@ -258,7 +258,8 @@ static const char *file_names[TGSI_FILE_COUNT] =
|
||||
"PRED",
|
||||
"SV",
|
||||
"IMMX",
|
||||
"TEMPX"
|
||||
"TEMPX",
|
||||
"RES"
|
||||
};
|
||||
|
||||
static boolean
|
||||
|
@@ -283,7 +283,8 @@ static const char *file_names[TGSI_FILE_COUNT] =
|
||||
"PRED",
|
||||
"SV",
|
||||
"IMMX",
|
||||
"TEMPX"
|
||||
"TEMPX",
|
||||
"RES"
|
||||
};
|
||||
|
||||
static boolean
|
||||
@@ -828,6 +829,15 @@ static const char *texture_names[TGSI_TEXTURE_COUNT] =
|
||||
"SHADOWRECT"
|
||||
};
|
||||
|
||||
static const char *type_names[] =
|
||||
{
|
||||
"UNORM",
|
||||
"SNORM",
|
||||
"SINT",
|
||||
"UINT",
|
||||
"FLOAT"
|
||||
};
|
||||
|
||||
static boolean
|
||||
match_inst_mnemonic(const char **pcur,
|
||||
const struct tgsi_opcode_info *info)
|
||||
@@ -1104,42 +1114,113 @@ static boolean parse_declaration( struct translate_ctx *ctx )
|
||||
cur = ctx->cur;
|
||||
eat_opt_white( &cur );
|
||||
if (*cur == ',' && !is_vs_input) {
|
||||
uint i;
|
||||
uint i, j;
|
||||
|
||||
cur++;
|
||||
eat_opt_white( &cur );
|
||||
for (i = 0; i < TGSI_SEMANTIC_COUNT; i++) {
|
||||
if (str_match_no_case( &cur, semantic_names[i] )) {
|
||||
const char *cur2 = cur;
|
||||
uint index;
|
||||
|
||||
if (is_digit_alpha_underscore( cur ))
|
||||
continue;
|
||||
eat_opt_white( &cur2 );
|
||||
if (*cur2 == '[') {
|
||||
cur2++;
|
||||
eat_opt_white( &cur2 );
|
||||
if (!parse_uint( &cur2, &index )) {
|
||||
report_error( ctx, "Expected literal integer" );
|
||||
return FALSE;
|
||||
if (file == TGSI_FILE_RESOURCE) {
|
||||
for (i = 0; i < TGSI_TEXTURE_COUNT; i++) {
|
||||
if (str_match_no_case(&cur, texture_names[i])) {
|
||||
if (!is_digit_alpha_underscore(cur)) {
|
||||
decl.Resource.Resource = i;
|
||||
break;
|
||||
}
|
||||
eat_opt_white( &cur2 );
|
||||
if (*cur2 != ']') {
|
||||
report_error( ctx, "Expected `]'" );
|
||||
return FALSE;
|
||||
}
|
||||
cur2++;
|
||||
|
||||
decl.Semantic.Index = index;
|
||||
|
||||
cur = cur2;
|
||||
}
|
||||
}
|
||||
if (i == TGSI_TEXTURE_COUNT) {
|
||||
report_error(ctx, "Expected texture target");
|
||||
return FALSE;
|
||||
}
|
||||
eat_opt_white( &cur );
|
||||
if (*cur != ',') {
|
||||
report_error( ctx, "Expected `,'" );
|
||||
return FALSE;
|
||||
}
|
||||
++cur;
|
||||
eat_opt_white( &cur );
|
||||
for (j = 0; j < 4; ++j) {
|
||||
for (i = 0; i < PIPE_TYPE_COUNT; ++i) {
|
||||
if (str_match_no_case(&cur, type_names[i])) {
|
||||
if (!is_digit_alpha_underscore(cur)) {
|
||||
switch (j) {
|
||||
case 0:
|
||||
decl.Resource.ReturnTypeX = i;
|
||||
break;
|
||||
case 1:
|
||||
decl.Resource.ReturnTypeY = i;
|
||||
break;
|
||||
case 2:
|
||||
decl.Resource.ReturnTypeZ = i;
|
||||
break;
|
||||
case 3:
|
||||
decl.Resource.ReturnTypeW = i;
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (i == PIPE_TYPE_COUNT) {
|
||||
if (j == 0 || j > 2) {
|
||||
report_error(ctx, "Expected type name");
|
||||
return FALSE;
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
const char *cur2 = cur;
|
||||
eat_opt_white( &cur2 );
|
||||
if (*cur2 == ',') {
|
||||
cur2++;
|
||||
eat_opt_white( &cur2 );
|
||||
cur = cur2;
|
||||
continue;
|
||||
} else
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (j < 4) {
|
||||
decl.Resource.ReturnTypeY =
|
||||
decl.Resource.ReturnTypeZ =
|
||||
decl.Resource.ReturnTypeW =
|
||||
decl.Resource.ReturnTypeX;
|
||||
}
|
||||
ctx->cur = cur;
|
||||
} else {
|
||||
for (i = 0; i < TGSI_SEMANTIC_COUNT; i++) {
|
||||
if (str_match_no_case( &cur, semantic_names[i] )) {
|
||||
const char *cur2 = cur;
|
||||
uint index;
|
||||
|
||||
decl.Declaration.Semantic = 1;
|
||||
decl.Semantic.Name = i;
|
||||
if (is_digit_alpha_underscore( cur ))
|
||||
continue;
|
||||
eat_opt_white( &cur2 );
|
||||
if (*cur2 == '[') {
|
||||
cur2++;
|
||||
eat_opt_white( &cur2 );
|
||||
if (!parse_uint( &cur2, &index )) {
|
||||
report_error( ctx, "Expected literal integer" );
|
||||
return FALSE;
|
||||
}
|
||||
eat_opt_white( &cur2 );
|
||||
if (*cur2 != ']') {
|
||||
report_error( ctx, "Expected `]'" );
|
||||
return FALSE;
|
||||
}
|
||||
cur2++;
|
||||
|
||||
ctx->cur = cur;
|
||||
break;
|
||||
decl.Semantic.Index = index;
|
||||
|
||||
cur = cur2;
|
||||
}
|
||||
|
||||
decl.Declaration.Semantic = 1;
|
||||
decl.Semantic.Name = i;
|
||||
|
||||
ctx->cur = cur;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (is_imm_array) {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
/**************************************************************************
|
||||
*
|
||||
* Copyright 2009 VMware, Inc.
|
||||
* Copyright 2009-2010 VMware, Inc.
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
@@ -47,6 +47,7 @@ union tgsi_any_token {
|
||||
struct tgsi_declaration_range decl_range;
|
||||
struct tgsi_declaration_dimension decl_dim;
|
||||
struct tgsi_declaration_semantic decl_semantic;
|
||||
struct tgsi_declaration_resource decl_resource;
|
||||
struct tgsi_immediate imm;
|
||||
union tgsi_immediate_data imm_data;
|
||||
struct tgsi_instruction insn;
|
||||
@@ -137,6 +138,16 @@ struct ureg_program
|
||||
struct ureg_src sampler[PIPE_MAX_SAMPLERS];
|
||||
unsigned nr_samplers;
|
||||
|
||||
struct {
|
||||
unsigned index;
|
||||
unsigned target;
|
||||
unsigned return_type_x;
|
||||
unsigned return_type_y;
|
||||
unsigned return_type_z;
|
||||
unsigned return_type_w;
|
||||
} resource[PIPE_MAX_SHADER_RESOURCES];
|
||||
unsigned nr_resources;
|
||||
|
||||
unsigned temps_active[UREG_MAX_TEMP / 32];
|
||||
unsigned nr_temps;
|
||||
|
||||
@@ -578,6 +589,41 @@ struct ureg_src ureg_DECL_sampler( struct ureg_program *ureg,
|
||||
return ureg->sampler[0];
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate a new shader resource.
|
||||
*/
|
||||
struct ureg_src
|
||||
ureg_DECL_resource(struct ureg_program *ureg,
|
||||
unsigned index,
|
||||
unsigned target,
|
||||
unsigned return_type_x,
|
||||
unsigned return_type_y,
|
||||
unsigned return_type_z,
|
||||
unsigned return_type_w)
|
||||
{
|
||||
struct ureg_src reg = ureg_src_register(TGSI_FILE_RESOURCE, index);
|
||||
uint i;
|
||||
|
||||
for (i = 0; i < ureg->nr_resources; i++) {
|
||||
if (ureg->resource[i].index == index) {
|
||||
return reg;
|
||||
}
|
||||
}
|
||||
|
||||
if (i < PIPE_MAX_SHADER_RESOURCES) {
|
||||
ureg->resource[i].index = index;
|
||||
ureg->resource[i].target = target;
|
||||
ureg->resource[i].return_type_x = return_type_x;
|
||||
ureg->resource[i].return_type_y = return_type_y;
|
||||
ureg->resource[i].return_type_z = return_type_z;
|
||||
ureg->resource[i].return_type_w = return_type_w;
|
||||
ureg->nr_resources++;
|
||||
return reg;
|
||||
}
|
||||
|
||||
assert(0);
|
||||
return reg;
|
||||
}
|
||||
|
||||
static int
|
||||
match_or_expand_immediate( const unsigned *v,
|
||||
@@ -821,6 +867,7 @@ ureg_emit_dst( struct ureg_program *ureg,
|
||||
assert(dst.File != TGSI_FILE_CONSTANT);
|
||||
assert(dst.File != TGSI_FILE_INPUT);
|
||||
assert(dst.File != TGSI_FILE_SAMPLER);
|
||||
assert(dst.File != TGSI_FILE_RESOURCE);
|
||||
assert(dst.File != TGSI_FILE_IMMEDIATE);
|
||||
assert(dst.File < TGSI_FILE_COUNT);
|
||||
|
||||
@@ -1205,6 +1252,36 @@ emit_decl_range2D(struct ureg_program *ureg,
|
||||
out[2].decl_dim.Index2D = index2D;
|
||||
}
|
||||
|
||||
static void
|
||||
emit_decl_resource(struct ureg_program *ureg,
|
||||
unsigned index,
|
||||
unsigned target,
|
||||
unsigned return_type_x,
|
||||
unsigned return_type_y,
|
||||
unsigned return_type_z,
|
||||
unsigned return_type_w )
|
||||
{
|
||||
union tgsi_any_token *out = get_tokens(ureg, DOMAIN_DECL, 3);
|
||||
|
||||
out[0].value = 0;
|
||||
out[0].decl.Type = TGSI_TOKEN_TYPE_DECLARATION;
|
||||
out[0].decl.NrTokens = 3;
|
||||
out[0].decl.File = TGSI_FILE_RESOURCE;
|
||||
out[0].decl.UsageMask = 0xf;
|
||||
out[0].decl.Interpolate = TGSI_INTERPOLATE_CONSTANT;
|
||||
|
||||
out[1].value = 0;
|
||||
out[1].decl_range.First = index;
|
||||
out[1].decl_range.Last = index;
|
||||
|
||||
out[2].value = 0;
|
||||
out[2].decl_resource.Resource = target;
|
||||
out[2].decl_resource.ReturnTypeX = return_type_x;
|
||||
out[2].decl_resource.ReturnTypeY = return_type_y;
|
||||
out[2].decl_resource.ReturnTypeZ = return_type_z;
|
||||
out[2].decl_resource.ReturnTypeW = return_type_w;
|
||||
}
|
||||
|
||||
static void
|
||||
emit_immediate( struct ureg_program *ureg,
|
||||
const unsigned *v,
|
||||
@@ -1341,6 +1418,16 @@ static void emit_decls( struct ureg_program *ureg )
|
||||
ureg->sampler[i].Index, 1 );
|
||||
}
|
||||
|
||||
for (i = 0; i < ureg->nr_resources; i++) {
|
||||
emit_decl_resource(ureg,
|
||||
ureg->resource[i].index,
|
||||
ureg->resource[i].target,
|
||||
ureg->resource[i].return_type_x,
|
||||
ureg->resource[i].return_type_y,
|
||||
ureg->resource[i].return_type_z,
|
||||
ureg->resource[i].return_type_w);
|
||||
}
|
||||
|
||||
if (ureg->const_decls.nr_constant_ranges) {
|
||||
for (i = 0; i < ureg->const_decls.nr_constant_ranges; i++) {
|
||||
emit_decl_range(ureg,
|
||||
|
@@ -270,6 +270,15 @@ struct ureg_src
|
||||
ureg_DECL_sampler( struct ureg_program *,
|
||||
unsigned index );
|
||||
|
||||
struct ureg_src
|
||||
ureg_DECL_resource(struct ureg_program *,
|
||||
unsigned index,
|
||||
unsigned target,
|
||||
unsigned return_type_x,
|
||||
unsigned return_type_y,
|
||||
unsigned return_type_z,
|
||||
unsigned return_type_w );
|
||||
|
||||
|
||||
static INLINE struct ureg_src
|
||||
ureg_imm4f( struct ureg_program *ureg,
|
||||
@@ -733,6 +742,66 @@ static INLINE void ureg_##op( struct ureg_program *ureg, \
|
||||
}
|
||||
|
||||
|
||||
#define OP14( op ) \
|
||||
static INLINE void ureg_##op( struct ureg_program *ureg, \
|
||||
struct ureg_dst dst, \
|
||||
struct ureg_src src0, \
|
||||
struct ureg_src src1, \
|
||||
struct ureg_src src2, \
|
||||
struct ureg_src src3 ) \
|
||||
{ \
|
||||
unsigned opcode = TGSI_OPCODE_##op; \
|
||||
unsigned insn = ureg_emit_insn(ureg, \
|
||||
opcode, \
|
||||
dst.Saturate, \
|
||||
dst.Predicate, \
|
||||
dst.PredNegate, \
|
||||
dst.PredSwizzleX, \
|
||||
dst.PredSwizzleY, \
|
||||
dst.PredSwizzleZ, \
|
||||
dst.PredSwizzleW, \
|
||||
1, \
|
||||
4).insn_token; \
|
||||
ureg_emit_dst( ureg, dst ); \
|
||||
ureg_emit_src( ureg, src0 ); \
|
||||
ureg_emit_src( ureg, src1 ); \
|
||||
ureg_emit_src( ureg, src2 ); \
|
||||
ureg_emit_src( ureg, src3 ); \
|
||||
ureg_fixup_insn_size( ureg, insn ); \
|
||||
}
|
||||
|
||||
|
||||
#define OP15( op ) \
|
||||
static INLINE void ureg_##op( struct ureg_program *ureg, \
|
||||
struct ureg_dst dst, \
|
||||
struct ureg_src src0, \
|
||||
struct ureg_src src1, \
|
||||
struct ureg_src src2, \
|
||||
struct ureg_src src3, \
|
||||
struct ureg_src src4 ) \
|
||||
{ \
|
||||
unsigned opcode = TGSI_OPCODE_##op; \
|
||||
unsigned insn = ureg_emit_insn(ureg, \
|
||||
opcode, \
|
||||
dst.Saturate, \
|
||||
dst.Predicate, \
|
||||
dst.PredNegate, \
|
||||
dst.PredSwizzleX, \
|
||||
dst.PredSwizzleY, \
|
||||
dst.PredSwizzleZ, \
|
||||
dst.PredSwizzleW, \
|
||||
1, \
|
||||
5).insn_token; \
|
||||
ureg_emit_dst( ureg, dst ); \
|
||||
ureg_emit_src( ureg, src0 ); \
|
||||
ureg_emit_src( ureg, src1 ); \
|
||||
ureg_emit_src( ureg, src2 ); \
|
||||
ureg_emit_src( ureg, src3 ); \
|
||||
ureg_emit_src( ureg, src4 ); \
|
||||
ureg_fixup_insn_size( ureg, insn ); \
|
||||
}
|
||||
|
||||
|
||||
/* Use a template include to generate a correctly-typed ureg_OP()
|
||||
* function for each TGSI opcode:
|
||||
*/
|
||||
|
@@ -1250,6 +1250,110 @@ This opcode is the inverse of :opcode:`DFRACEXP`.
|
||||
dst.zw = \sqrt{src.zw}
|
||||
|
||||
|
||||
.. _resourceopcodes:
|
||||
|
||||
Resource Access Opcodes
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Those opcodes follow very closely semantics of the respective Direct3D
|
||||
instructions. If in doubt double check Direct3D documentation.
|
||||
|
||||
.. opcode:: LOAD - Simplified alternative to the "SAMPLE" instruction.
|
||||
Using the provided integer address, LOAD fetches data
|
||||
from the specified buffer/texture without any filtering.
|
||||
The source data may come from any resource type other
|
||||
than CUBE.
|
||||
LOAD dst, address, resource
|
||||
e.g.
|
||||
LOAD TEMP[0], TEMP[1], RES[0]
|
||||
|
||||
.. opcode:: LOAD_MS - Just like LOAD but allows fetch data from
|
||||
multi-sampled surfaces.
|
||||
|
||||
.. opcode:: SAMPLE - Using provided address, sample data from the
|
||||
specified texture using the filtering mode identified
|
||||
by the gven sampler. The source data may come from
|
||||
any resource type other than buffers.
|
||||
SAMPLE dst, address, resource, sampler
|
||||
e.g.
|
||||
SAMPLE TEMP[0], TEMP[1], RES[0], SAMP[0]
|
||||
|
||||
.. opcode:: SAMPLE_B - Just like the SAMPLE instruction with the
|
||||
exception that an additiona bias is applied to the
|
||||
level of detail computed as part of the instruction
|
||||
execution.
|
||||
SAMPLE_B dst, address, resource, sampler, lod_bias
|
||||
e.g.
|
||||
SAMPLE_B TEMP[0], TEMP[1], RES[0], SAMP[0], TEMP[2].x
|
||||
|
||||
.. opcode:: SAMPLE_C - Similar to the SAMPLE instruction but it
|
||||
performs a comparison filter. The operands to SAMPLE_C
|
||||
are identical to SAMPLE, except that tere is an additional
|
||||
float32 operand, reference value, which must be a register
|
||||
with single-component, or a scalar literal.
|
||||
SAMPLE_C makes the hardware use the current samplers
|
||||
compare_func (in pipe_sampler_state) to compare
|
||||
reference value against the red component value for the
|
||||
surce resource at each texel that the currently configured
|
||||
texture filter covers based on the provided coordinates.
|
||||
SAMPLE_C dst, address, resource.r, sampler, ref_value
|
||||
e.g.
|
||||
SAMPLE_C TEMP[0], TEMP[1], RES[0].r, SAMP[0], TEMP[2].x
|
||||
|
||||
.. opcode:: SAMPLE_C_LZ - Same as SAMPLE_C, but LOD is 0 and derivatives
|
||||
are ignored. The LZ stands for level-zero.
|
||||
SAMPLE_C_LZ dst, address, resource.r, sampler, ref_value
|
||||
e.g.
|
||||
SAMPLE_C_LZ TEMP[0], TEMP[1], RES[0].r, SAMP[0], TEMP[2].x
|
||||
|
||||
|
||||
.. opcode:: SAMPLE_D - SAMPLE_D is identical to the SAMPLE opcode except
|
||||
that the derivatives for the source address in the x
|
||||
direction and the y direction are provided by extra
|
||||
parameters.
|
||||
SAMPLE_D dst, address, resource, sampler, der_x, der_y
|
||||
e.g.
|
||||
SAMPLE_D TEMP[0], TEMP[1], RES[0], SAMP[0], TEMP[2], TEMP[3]
|
||||
|
||||
.. opcode:: SAMPLE_L - SAMPLE_L is identical to the SAMPLE opcode except
|
||||
that the LOD is provided directly as a scalar value,
|
||||
representing no anisotropy. Source addresses A channel
|
||||
is used as the LOD.
|
||||
SAMPLE_L dst, address, resource, sampler
|
||||
e.g.
|
||||
SAMPLE_L TEMP[0], TEMP[1], RES[0], SAMP[0]
|
||||
|
||||
|
||||
.. opcode:: GATHER4 - Gathers the four texels to be used in a bi-linear
|
||||
filtering operation and packs them into a single register.
|
||||
Only woth with 2D, 2D array, cubemaps, and cubemaps arrays.
|
||||
For 2D textures, only the addressing modes of the sampler and
|
||||
the top level of any mip pyramid are used. Set W to zero.
|
||||
It behaves like the SAMPLE instruction, but a filtered
|
||||
sample is not generated. The four samples that contribute
|
||||
to filtering are places into xyzw in cunter-clockwise order,
|
||||
starting with the (u,v) texture coordinate delta at the
|
||||
following locations (-, +), (+, +), (+, -), (-, -), where
|
||||
the magnitude of the deltas are half a texel.
|
||||
|
||||
|
||||
.. opcode:: RESINFO - query the dimentions of a given input buffer.
|
||||
dst receives width, height, depth or array size and
|
||||
total mip count (also can be slected by writemask).
|
||||
RESINFO dst, src_mip_level, resource
|
||||
e.g.
|
||||
RESINFO TEMP[0], TEMP[1].x, RES[0]
|
||||
|
||||
.. opcode:: SAMPLE_POS - query the position of a given sample.
|
||||
dst receives float4 (x, y, 0, 0) indicated where the
|
||||
sample is located. If the resource is not a multi-sample
|
||||
resource and not a render target, the result is 0.
|
||||
|
||||
.. opcode:: SAMPLE_INFO - dst receives number of components in x.
|
||||
If the resource is not a multi-sample resource and
|
||||
not a render target, the result is 0.
|
||||
|
||||
|
||||
Explanation of symbols used
|
||||
------------------------------
|
||||
|
||||
@@ -1332,6 +1436,8 @@ wrapping when interpolating by the rasteriser. If TGSI_CYLINDRICAL_WRAP_X
|
||||
is set to 1, the X component should be interpolated according to cylindrical
|
||||
wrapping rules.
|
||||
|
||||
If file is TGSI_FILE_RESOURCE, a Declaration Resource token follows.
|
||||
|
||||
|
||||
Declaration Semantic
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@@ -1475,6 +1581,23 @@ is a writable stencil reference value. Only the Y component is writable.
|
||||
This allows the fragment shader to change the fragments stencilref value.
|
||||
|
||||
|
||||
Declaration Resource
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Follows Declaration token if file is TGSI_FILE_RESOURCE.
|
||||
|
||||
DCL RES[#], resource, type(s)
|
||||
|
||||
Declares a shader input resource and assigns it to a RES[#]
|
||||
register.
|
||||
|
||||
resource can be one of BUFFER, 1D, 2D, 3D, CUBE, 1DArray and
|
||||
2DArray.
|
||||
|
||||
type must be 1 or 4 entries (if specifying on a per-component
|
||||
level) out of UNORM, SNORM, SINT, UINT and FLOAT.
|
||||
|
||||
|
||||
Properties
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
@@ -33,6 +33,16 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
enum pipe_type {
|
||||
PIPE_TYPE_UNORM = 0,
|
||||
PIPE_TYPE_SNORM,
|
||||
PIPE_TYPE_SINT,
|
||||
PIPE_TYPE_UINT,
|
||||
PIPE_TYPE_FLOAT,
|
||||
PIPE_TYPE_COUNT
|
||||
};
|
||||
|
||||
/**
|
||||
* Texture/surface image formats (preliminary)
|
||||
*/
|
||||
|
@@ -75,6 +75,7 @@ enum tgsi_file_type {
|
||||
TGSI_FILE_SYSTEM_VALUE =9,
|
||||
TGSI_FILE_IMMEDIATE_ARRAY =10,
|
||||
TGSI_FILE_TEMPORARY_ARRAY =11,
|
||||
TGSI_FILE_RESOURCE =12,
|
||||
TGSI_FILE_COUNT /**< how many TGSI_FILE_ types */
|
||||
};
|
||||
|
||||
@@ -153,6 +154,14 @@ struct tgsi_declaration_semantic
|
||||
unsigned Padding : 8;
|
||||
};
|
||||
|
||||
struct tgsi_declaration_resource {
|
||||
unsigned Resource : 8; /**< one of TGSI_TEXTURE_ */
|
||||
unsigned ReturnTypeX : 6; /**< one of enum pipe_type */
|
||||
unsigned ReturnTypeY : 6; /**< one of enum pipe_type */
|
||||
unsigned ReturnTypeZ : 6; /**< one of enum pipe_type */
|
||||
unsigned ReturnTypeW : 6; /**< one of enum pipe_type */
|
||||
};
|
||||
|
||||
#define TGSI_IMM_FLOAT32 0
|
||||
#define TGSI_IMM_UINT32 1
|
||||
#define TGSI_IMM_INT32 2
|
||||
@@ -339,7 +348,22 @@ struct tgsi_property_data {
|
||||
#define TGSI_OPCODE_CASE 142
|
||||
#define TGSI_OPCODE_DEFAULT 143
|
||||
#define TGSI_OPCODE_ENDSWITCH 144
|
||||
#define TGSI_OPCODE_LAST 145
|
||||
|
||||
/* resource related opcodes */
|
||||
#define TGSI_OPCODE_LOAD 145
|
||||
#define TGSI_OPCODE_LOAD_MS 146
|
||||
#define TGSI_OPCODE_SAMPLE 147
|
||||
#define TGSI_OPCODE_SAMPLE_B 148
|
||||
#define TGSI_OPCODE_SAMPLE_C 149
|
||||
#define TGSI_OPCODE_SAMPLE_C_LZ 150
|
||||
#define TGSI_OPCODE_SAMPLE_D 151
|
||||
#define TGSI_OPCODE_SAMPLE_L 152
|
||||
#define TGSI_OPCODE_GATHER4 153
|
||||
#define TGSI_OPCODE_RESINFO 154
|
||||
#define TGSI_OPCODE_SAMPLE_POS 155
|
||||
#define TGSI_OPCODE_SAMPLE_INFO 156
|
||||
|
||||
#define TGSI_OPCODE_LAST 157
|
||||
|
||||
#define TGSI_SAT_NONE 0 /* do not saturate */
|
||||
#define TGSI_SAT_ZERO_ONE 1 /* clamp to [0,1] */
|
||||
|
@@ -62,6 +62,7 @@ extern "C" {
|
||||
#define PIPE_MAX_GEOMETRY_SAMPLERS 16
|
||||
#define PIPE_MAX_SHADER_INPUTS 32
|
||||
#define PIPE_MAX_SHADER_OUTPUTS 32
|
||||
#define PIPE_MAX_SHADER_RESOURCES 32
|
||||
#define PIPE_MAX_TEXTURE_LEVELS 16
|
||||
#define PIPE_MAX_SO_BUFFERS 4
|
||||
|
||||
|
@@ -24,6 +24,7 @@ progs = [
|
||||
'gs-test',
|
||||
'shader-leak',
|
||||
'tri-gs',
|
||||
'quad-sample',
|
||||
]
|
||||
|
||||
for name in progs:
|
||||
|
Reference in New Issue
Block a user