@@ -19,6 +19,19 @@ ${define_explicit_type_extensions(DTYPE)}
1919
2020${define_active_storage_type(STORAGE)}
2121
22+ $if TO_STAGING:
23+ #define TO_STAGING
24+ $if COALESCED_WRITES:
25+ #define COALESCED_WRITES
26+
27+ // The coalesced path writes contiguous NCHW staging offsets directly and does
28+ // not honor buf_meta / buf_layout, so it is only valid for the staging
29+ // destination. Fail the build if a coalesced non-staging (clone) variant is
30+ // ever configured.
31+ #if defined(COALESCED_WRITES) && ! defined(TO_STAGING)
32+ #error "COALESCED_WRITES requires TO_STAGING"
33+ #endif
34+
2235layout (std430) buffer ;
2336
2437#include "indexing.glslh"
@@ -39,17 +52,53 @@ $if not TO_STAGING:
3952 ${layout_declare_spec_const(C, "int ", "buf_layout", "CONTIG_LAYOUT_INT")}
4053
4154void main() {
55+ #ifdef COALESCED_WRITES
56+ // Output-centric dispatch: one thread per staging (NCHW) element. Consecutive
57+ // threads write consecutive staging offsets, so writes to the host-visible
58+ // staging buffer are fully coalesced. On a discrete GPU the staging buffer is
59+ // PCIe-backed, where coalescing the writes matters far more than coalescing
60+ // the (VRAM-cached) texture reads -- even though each texel is now fetched up
61+ // to 4 times, once per component. On unified-memory (mobile) GPUs the extra
62+ // fetches are a net loss, so this variant is gated to discrete GPUs.
63+ const int oi = int (gl_GlobalInvocationID.x);
64+ const int W = inp.sizes.x;
65+ const int H = inp.sizes.y;
66+ const int C = inp.sizes.z;
67+ const int N = inp.sizes.w;
68+ if (oi >= W * H * C * N) {
69+ return ;
70+ }
71+
72+ TensorIndex4D tidx;
73+ tidx.data.x = oi % W;
74+ tidx.data.y = (oi / W) % H;
75+ tidx.data.z = (oi / (W * H)) % C;
76+ tidx.data.w = oi / (W * H * C);
77+
78+ const TextureElementIndex tex =
79+ tensor4d_idx_to_texture_element_idx_simple(inp, tidx, in_layout);
80+ #ifdef USING_TEXTURE2D
81+ const VEC4_T intex = texelFetch(t_in, tex.pos.xy, 0 );
82+ #else
83+ const VEC4_T intex = texelFetch(t_in, tex.pos, 0 );
84+ #endif
85+ buf_out[oi] = BUF_T(intex[tex.comp]);
86+ #else
87+ // Texel-centric dispatch: one thread per texture texel, writing up to 4 packed
88+ // components. Reads are coalesced (one fetch per texel); writes are strided.
89+ // Preferred on unified-memory GPUs (mobile) where the staging buffer is not
90+ // PCIe-backed, so write coalescing buys nothing and the single fetch wins.
4291 const ivec3 pos = ivec3 (gl_GlobalInvocationID);
4392 if (out_of_bounds(pos, inp)) {
4493 return ;
4594 }
4695
4796 TensorIndex4D tidx = texture_pos_to_tensor4d_idx_simple(inp, pos, in_layout);
48- #ifdef USING_TEXTURE2D
97+ #ifdef USING_TEXTURE2D
4998 const VEC4_T intex = texelFetch(t_in, pos.xy, 0 );
50- #else
99+ #else
51100 const VEC4_T intex = texelFetch(t_in, pos, 0 );
52- #endif
101+ #endif
53102
54103 const int packed_dim = get_packed_dim(in_layout);
55104 int packed_dim_val;
@@ -77,16 +126,17 @@ void main() {
77126 int limit = min (4 , packed_dim_size - packed_dim_val);
78127
79128 for (int comp = 0 ; comp < limit; comp++ ) {
80- $if TO_STAGING:
81- // Compute contiguous NCHW index
82- int nchwi = tidx.data.x
83- + tidx.data.y * inp.sizes.x
84- + tidx.data.z * inp.sizes.x * inp.sizes.y
85- + tidx.data.w * inp.sizes.x * inp.sizes.y * inp.sizes.z;
86- buf_out[nchwi] = BUF_T(intex[comp]);
87- $else :
88- int bufi = tensor4d_idx_to_buf_idx(buf_meta, tidx, buf_layout);
89- buf_out[bufi] = BUF_T(intex[comp]);
129+ #ifdef TO_STAGING
130+ // Staging buffer is contiguous NCHW; compute the flat offset directly.
131+ int nchwi = tidx.data.x
132+ + tidx.data.y * inp.sizes.x
133+ + tidx.data.z * inp.sizes.x * inp.sizes.y
134+ + tidx.data.w * inp.sizes.x * inp.sizes.y * inp.sizes.z;
135+ buf_out[nchwi] = BUF_T(intex[comp]);
136+ #else
137+ int bufi = tensor4d_idx_to_buf_idx(buf_meta, tidx, buf_layout);
138+ buf_out[bufi] = BUF_T(intex[comp]);
139+ #endif
90140
91141 if (packed_dim == 0 ) {
92142 tidx.data.x++ ;
@@ -98,4 +148,5 @@ void main() {
98148 tidx.data.w++ ;
99149 }
100150 }
151+ #endif
101152}
0 commit comments