Skip to content
Snippets Groups Projects

vkd3d-shader/hlsl: Broaden resources support v2, part 2.

Merged Francisco Casas requested to merge fcasas/vkd3d:master6_part_2 into master

Mainly comprises support for allocating arrays of resources, and loading from them, for both SM1 and SM4.

Edited by Francisco Casas

Merge request reports

Loading
Loading

Activity

Filter activity
  • Approvals
  • Assignees & reviewers
  • Comments (from bots)
  • Comments (from users)
  • Commits & branches
  • Edits
  • Labels
  • Lock status
  • Mentions
  • Merge request status
  • Tracking
  • Elizabeth Figura
  • Elizabeth Figura
  • Elizabeth Figura
  • Elizabeth Figura
  • Elizabeth Figura
  • Elizabeth Figura
  • Elizabeth Figura
  • Elizabeth Figura
  • Elizabeth Figura
    Elizabeth Figura @zfigura started a thread on commit 2d2b7d53
  • 390 390 * and the buffer_offset instead. */
    391 391 struct hlsl_reg regs[HLSL_REGSET_LAST + 1];
    392 392
    393 struct {
    394 bool used;
    395 } *objects_usage[HLSL_REGSET_LAST_OBJECT + 1];
    396
    • Comment on lines +393 to +396

      Thought: what if we tracked this per component instead?

      The current usage is close enough to RA / backend logic that I don't think it matters much from a design perspective, but I do suspect that in general this would make things a bit simpler, if only because it's a single array instead of several.

      In fact, we could potentially expand this in the future to store other per-component logic. One particularly salient thing that comes to mind is tracking whether we need to use a bool / int variable for sm1 uniforms—which itself determines the size of each register set.

      I think the downside is that request_object_registers_for_allocation() has to do a bit more work, but at least it's very clear and straightforward work.

      (Also, style nitpick: imbalanced braces.)

    • Author Developer

      I don't like the idea very much. We often would want (in this MR patches and later) to iterate over the elements in a specific regset. These cases are easier now:

      unsigned int i, count = var->data_type->reg_size[regset];
      
      for (i = 0; i < count; ++i)
      {
          /* Directly use var->objects_usage[regset][i] */
      }

      than they would be if objects_usage is arranged per-component:

      unsigned int k, i, count = hlsl_type_component_count(var->data_type);
      
      i = 0;
      for (k = 0; k < count; ++k)
      {
          hlsl_type *component_type = hlsl_type_get_component_type(ctx, var->data_type, k);
      
          if (regset != hlsl_type_get_regset(ctx, component_type))
              continue
      
          /* Use var->objects_usage[k], knowing that its offset in the regset is i. */
      
          ++i;
      }

      In fact, we could potentially expand this in the future to store other per-component logic. One particularly salient thing that comes to mind is tracking whether we need to use a bool / int variable for sm1 uniforms—which itself determines the size of each register set.

      I think that in this case we would rather store this information variable-wise. Since we only need the maximum register index used, and no information for every component, as we do for samplers (sampler_dim), and textures in SM 5 (we need to write a resource entry for every used component).

    • I don't like the idea very much. We often would want (in this MR patches and later) to iterate over the elements in a specific regset.

      How many such cases are there? I can't easily think of cases where we need to know the register offset and haven't already gotten that information from RA.

      The larger loop looks pretty reasonable to me; if nothing else, it's quite clear what it's doing.

      In fact, we could potentially expand this in the future to store other per-component logic. One particularly salient thing that comes to mind is tracking whether we need to use a bool / int variable for sm1 uniforms—which itself determines the size of each register set.

      I think that in this case we would rather store this information variable-wise. Since we only need the maximum register index used, and no information for every component, as we do for samplers (sampler_dim), and textures in SM 5 (we need to write a resource entry for every used component).

      I don't understand, how can this information be stored per variable? IIRC sm1 cares about things like int and bool on a per-register basis, or at least a per-struct-field basis.

    • Author Developer

      I don't like the idea very much. We often would want (in this MR patches and later) to iterate over the elements in a specific regset.

      How many such cases are there? I can't easily think of cases where we need to know the register offset and haven't already gotten that information from RA.

      The larger loop looks pretty reasonable to me; if nothing else, it's quite clear what it's doing.

      Okay, it is not as bad as I imagine. Perhaps I should do the cost assessments when I am fresh in the morning and not when I tired.

      Still, there are 5 places where it would be better to iterate over a regset: track_object_components_usage(), calculate_resource_register_counts(), write_sm1_sampler_dcls(), write_sm4_dcl_samplers(), and write_sm4_dcl_textures(); but iteration per-component it is useful for promoting resource components into separate variables for SM 5.1.

      In fact, we could potentially expand this in the future to store other per-component logic. One particularly salient thing that comes to mind is tracking whether we need to use a bool / int variable for sm1 uniforms—which itself determines the size of each register set.

      I think that in this case we would rather store this information variable-wise. Since we only need the maximum register index used, and no information for every component, as we do for samplers (sampler_dim), and textures in SM 5 (we need to write a resource entry for every used component).

      I don't understand, how can this information be stored per variable? IIRC sm1 cares about things like int and bool on a per-register basis, or at least a per-struct-field basis.

      I probably didn't understood this the first time, I thought you were referring to the register range that's allocated, which goes from 0 to the maximum used component (which can be stored in var->regs[HLSL_REGSET_NUMERIC].count).

      If I understand correctly, you are saying that in SM1 there are cases where a uniform variable (or field) can require the size of a bool or a int according to how it is used. I am not familiarized with this behavior. Can you provide an example?

      Edited by Francisco Casas
    • Okay, it is not as bad as I imagine. Perhaps I should do the cost assessments when I am fresh in the morning and not when I tired.

      Still, there are 5 places where it would be better to iterate over a regset: track_object_components_usage(), calculate_resource_register_counts(), write_sm1_sampler_dcls(), write_sm4_dcl_samplers(), and write_sm4_dcl_textures(); but iteration per-component it is useful for promoting resource components into separate variables for SM 5.1.

      Hrm, I didn't quite think this through completely, I forgot that register allocation is still only done per-variable. Yeah, so it would require that for every time we're declaring a texture.

      If we need to track anything per-component, it still may be best to do it that way, though.

      I probably didn't understood this the first time, I thought you were referring to the register range that's allocated, which goes from 0 to the maximum used component (which can be stored in var->regs[HLSL_REGSET_NUMERIC].count).

      If I understand correctly, you are saying that in SM1 there are cases where a uniform variable (or field) can require the size of a bool or a int according to how it is used. I am not familiarized with this behavior. Can you provide an example?

      Most of sm1 uses float uniforms, even where they're declared with non-float type. SM 3.0 introduces the first flow control, and its flow control instructions—and only those instructions—take non-float types. Specifically "if" takes a bool type, and "loop" takes an integer type. Consider the following shader:

      uniform struct
      {
        float f;
        bool b;
        int i;
      } a;
      
      float4 main() : sv_target
      {
          float x = a.f + a.i + a.b;
          if (a.b)
              x += 2;
          return x;
      }

      Ultimately "a.b" is allocated to both the float and bool register sets. (You can get a similar effect for ints by declaring a loop that executes for i iterations, but I didn't bother with that.)

      This is true without the struct too, of course, but the struct shows that there are interesting consequences wrt allocation.

      Granted, I suppose we don't really need to do this tracking per-component—we can just do it the same way as we do textures...

    • Author Developer

      That example alone doesn't seem to be triggering the allocation of both bool and float registers for a.b because of compiler optimizations[1], but I changed the shader a little to prevent them:

      sampler sam;
      
      uniform struct
      {
        float f;
        bool b;
        int i;
      } a;
      
      float4 main() : sv_target
      {
          float x = a.f + a.i + a.b;
          if (a.b)
              x += tex2D(sam, float2(0, 0));
          return x;
      }

      I get this register table:

      //   Name         Reg   Size
      //   ------------ ----- ----
      //   a            b0       2
      //   a            c0       3
      //   sam          s0       1

      Note that the offset of the bool a.b is the same as the offset of the float a.b.

      So, we will need a function similar to track_object_components_usage() but checking for the usage of bool components, to determine the size of the variable in the REGSET_BOOL (?). Still, I don't think tracking per-component would be necessary.


      [1] This implies that whether to expect bools in the input signature or not is totally a decision of the compiler.

      Perhaps we can get away with just putting floats in the input signature and casting to float within the shader. That may not work if some applications hardcode the input signature, but also, I may be really impossible to mimic compiler optimizations 1:1. For instance, this slightly modified shader doesn't require bool registers:

      sampler sam;
      
      uniform struct
      {
        float f;
        bool b;
        int i;
      } a;
      
      bool k;
      
      float4 main() : sv_target
      {
          float x = a.f + a.i + a.b;
          if (a.b || k)
              x += tex2D(sam, float2(0, 0));
          return x;
      }
      Edited by Francisco Casas
    • That example alone doesn't seem to be triggering the allocation of both bool and float registers for a.b because of compiler optimizations[1], but I changed the shader a little to prevent them:

      Curious, it does for me (d3dcompiler_47 from winetricks). But yes, it probably is not critical to match native exactly.

      So, we will need a function similar to track_object_components_usage() but checking for the usage of bool components, to determine the size of the variable in the REGSET_BOOL (?). Still, I don't think tracking per-component would be necessary.

      We could potentially do it in the same function, even (and call it something that implies it's not specific to objects).

    • Please register or sign in to reply
  • Elizabeth Figura
  • Elizabeth Figura
  • Elizabeth Figura
  • Elizabeth Figura
  • Elizabeth Figura
  • Elizabeth Figura
  • Elizabeth Figura
  • I just noticed, the change to hlsl_ir_resource_load() needs the clone function to be updated.

  • Giovanni Mascellani
  • Francisco Casas changed the description

    changed the description

  • Loading
  • Loading
  • Loading
  • Loading
  • Loading
  • Loading
  • Loading
  • Loading
  • Loading
  • Loading
  • Please register or sign in to reply
    Loading