@@ -10,11 +10,84 @@ use graphene_core::transform::Footprint;
10
10
use wgpu:: util:: { DeviceExt , TextureDataOrder } ;
11
11
use wgpu:: { Extent3d , TextureDescriptor , TextureDimension , TextureFormat , TextureUsages } ;
12
12
13
+ /// Uploads CPU image data to a GPU texture
14
+ ///
15
+ /// Creates a new WGPU texture with RGBA8UnormSrgb format and uploads the provided
16
+ /// image data. The texture is configured for binding, copying, and source operations.
17
+ fn upload_to_texture ( device : & std:: sync:: Arc < wgpu:: Device > , queue : & std:: sync:: Arc < wgpu:: Queue > , image : & Raster < CPU > ) -> wgpu:: Texture {
18
+ let rgba8_data: Vec < SRGBA8 > = image. data . iter ( ) . map ( |x| ( * x) . into ( ) ) . collect ( ) ;
19
+
20
+ device. create_texture_with_data (
21
+ queue,
22
+ & TextureDescriptor {
23
+ label : Some ( "upload_texture node texture" ) ,
24
+ size : Extent3d {
25
+ width : image. width ,
26
+ height : image. height ,
27
+ depth_or_array_layers : 1 ,
28
+ } ,
29
+ mip_level_count : 1 ,
30
+ sample_count : 1 ,
31
+ dimension : TextureDimension :: D2 ,
32
+ format : TextureFormat :: Rgba8UnormSrgb ,
33
+ usage : TextureUsages :: TEXTURE_BINDING | TextureUsages :: COPY_DST | TextureUsages :: COPY_SRC ,
34
+ view_formats : & [ ] ,
35
+ } ,
36
+ TextureDataOrder :: LayerMajor ,
37
+ bytemuck:: cast_slice ( rgba8_data. as_slice ( ) ) ,
38
+ )
39
+ }
40
+
41
+ /// Downloads GPU texture data to a CPU buffer
42
+ ///
43
+ /// Creates a buffer and adds a copy operation from the texture to the buffer
44
+ /// using the provided command encoder. Returns dimensions and the buffer for
45
+ /// later mapping and data extraction.
46
+ fn download_to_buffer ( device : & std:: sync:: Arc < wgpu:: Device > , encoder : & mut wgpu:: CommandEncoder , texture : & wgpu:: Texture ) -> ( u32 , u32 , wgpu:: Buffer ) {
47
+ let width = texture. width ( ) ;
48
+ let height = texture. height ( ) ;
49
+ let bytes_per_pixel = 4 ; // RGBA8
50
+ let buffer_size = ( width * height * bytes_per_pixel) as u64 ;
51
+
52
+ let output_buffer = device. create_buffer ( & wgpu:: BufferDescriptor {
53
+ label : Some ( "texture_download_buffer" ) ,
54
+ size : buffer_size,
55
+ usage : wgpu:: BufferUsages :: COPY_DST | wgpu:: BufferUsages :: MAP_READ ,
56
+ mapped_at_creation : false ,
57
+ } ) ;
58
+
59
+ encoder. copy_texture_to_buffer (
60
+ wgpu:: TexelCopyTextureInfo {
61
+ texture,
62
+ mip_level : 0 ,
63
+ origin : wgpu:: Origin3d :: ZERO ,
64
+ aspect : wgpu:: TextureAspect :: All ,
65
+ } ,
66
+ wgpu:: TexelCopyBufferInfo {
67
+ buffer : & output_buffer,
68
+ layout : wgpu:: TexelCopyBufferLayout {
69
+ offset : 0 ,
70
+ bytes_per_row : Some ( width * bytes_per_pixel) ,
71
+ rows_per_image : Some ( height) ,
72
+ } ,
73
+ } ,
74
+ Extent3d {
75
+ width,
76
+ height,
77
+ depth_or_array_layers : 1 ,
78
+ } ,
79
+ ) ;
80
+ ( width, height, output_buffer)
81
+ }
82
+
83
+ /// Passthrough conversion for GPU tables - no conversion needed
13
84
impl < ' i > Convert < Table < Raster < GPU > > , & ' i WgpuExecutor > for Table < Raster < GPU > > {
14
85
async fn convert ( self , _: Footprint , _converter : & ' i WgpuExecutor ) -> Table < Raster < GPU > > {
15
86
self
16
87
}
17
88
}
89
+
90
+ /// Converts CPU raster table to GPU by uploading each image to a texture
18
91
impl < ' i > Convert < Table < Raster < GPU > > , & ' i WgpuExecutor > for Table < Raster < CPU > > {
19
92
async fn convert ( self , _: Footprint , executor : & ' i WgpuExecutor ) -> Table < Raster < GPU > > {
20
93
let device = & executor. context . device ;
@@ -23,28 +96,7 @@ impl<'i> Convert<Table<Raster<GPU>>, &'i WgpuExecutor> for Table<Raster<CPU>> {
23
96
. iter ( )
24
97
. map ( |row| {
25
98
let image = row. element ;
26
- let rgba8_data: Vec < SRGBA8 > = image. data . iter ( ) . map ( |x| ( * x) . into ( ) ) . collect ( ) ;
27
-
28
- let texture = device. create_texture_with_data (
29
- queue,
30
- & TextureDescriptor {
31
- label : Some ( "upload_texture node texture" ) ,
32
- size : Extent3d {
33
- width : image. width ,
34
- height : image. height ,
35
- depth_or_array_layers : 1 ,
36
- } ,
37
- mip_level_count : 1 ,
38
- sample_count : 1 ,
39
- dimension : TextureDimension :: D2 ,
40
- format : TextureFormat :: Rgba8UnormSrgb ,
41
- // I don't know what usages are actually necessary
42
- usage : TextureUsages :: TEXTURE_BINDING | TextureUsages :: COPY_DST | TextureUsages :: COPY_SRC ,
43
- view_formats : & [ ] ,
44
- } ,
45
- TextureDataOrder :: LayerMajor ,
46
- bytemuck:: cast_slice ( rgba8_data. as_slice ( ) ) ,
47
- ) ;
99
+ let texture = upload_to_texture ( device, queue, image) ;
48
100
49
101
TableRow {
50
102
element : Raster :: new_gpu ( texture) ,
@@ -59,72 +111,51 @@ impl<'i> Convert<Table<Raster<GPU>>, &'i WgpuExecutor> for Table<Raster<CPU>> {
59
111
table
60
112
}
61
113
}
114
+
115
+ /// Converts single CPU raster to GPU by uploading to texture
116
+ impl < ' i > Convert < Raster < GPU > , & ' i WgpuExecutor > for Raster < CPU > {
117
+ async fn convert ( self , _: Footprint , executor : & ' i WgpuExecutor ) -> Raster < GPU > {
118
+ let device = & executor. context . device ;
119
+ let queue = & executor. context . queue ;
120
+ let texture = upload_to_texture ( device, queue, & self ) ;
121
+
122
+ queue. submit ( [ ] ) ;
123
+ Raster :: new_gpu ( texture)
124
+ }
125
+ }
126
+
127
+ /// Passthrough conversion for CPU tables - no conversion needed
62
128
impl < ' i > Convert < Table < Raster < CPU > > , & ' i WgpuExecutor > for Table < Raster < CPU > > {
63
129
async fn convert ( self , _: Footprint , _converter : & ' i WgpuExecutor ) -> Table < Raster < CPU > > {
64
130
self
65
131
}
66
132
}
133
+
134
+ /// Converts GPU raster table to CPU by downloading texture data in one go
135
+ ///
136
+ /// then asynchronously maps all buffers and processes the results.
67
137
impl < ' i > Convert < Table < Raster < CPU > > , & ' i WgpuExecutor > for Table < Raster < GPU > > {
68
138
async fn convert ( self , _: Footprint , executor : & ' i WgpuExecutor ) -> Table < Raster < CPU > > {
69
139
let device = & executor. context . device ;
70
140
let queue = & executor. context . queue ;
71
141
72
- // Create a single command encoder for all copy operations
73
142
let mut encoder = device. create_command_encoder ( & wgpu:: CommandEncoderDescriptor {
74
143
label : Some ( "batch_texture_download_encoder" ) ,
75
144
} ) ;
76
145
77
- // Collect all buffer and texture info for batch processing
78
146
let mut buffers_and_info = Vec :: new ( ) ;
79
147
80
148
for row in self . iter ( ) {
81
149
let gpu_raster = row. element ;
82
150
let texture = gpu_raster. data ( ) ;
83
151
84
- // Get texture dimensions
85
- let width = texture. width ( ) ;
86
- let height = texture. height ( ) ;
87
- let bytes_per_pixel = 4 ; // RGBA8
88
- let buffer_size = ( width * height * bytes_per_pixel) as u64 ;
89
-
90
- // Create a buffer to copy texture data to
91
- let output_buffer = device. create_buffer ( & wgpu:: BufferDescriptor {
92
- label : Some ( "texture_download_buffer" ) ,
93
- size : buffer_size,
94
- usage : wgpu:: BufferUsages :: COPY_DST | wgpu:: BufferUsages :: MAP_READ ,
95
- mapped_at_creation : false ,
96
- } ) ;
97
-
98
- // Add copy operation to the batch encoder
99
- encoder. copy_texture_to_buffer (
100
- wgpu:: TexelCopyTextureInfo {
101
- texture,
102
- mip_level : 0 ,
103
- origin : wgpu:: Origin3d :: ZERO ,
104
- aspect : wgpu:: TextureAspect :: All ,
105
- } ,
106
- wgpu:: TexelCopyBufferInfo {
107
- buffer : & output_buffer,
108
- layout : wgpu:: TexelCopyBufferLayout {
109
- offset : 0 ,
110
- bytes_per_row : Some ( width * bytes_per_pixel) ,
111
- rows_per_image : Some ( height) ,
112
- } ,
113
- } ,
114
- Extent3d {
115
- width,
116
- height,
117
- depth_or_array_layers : 1 ,
118
- } ,
119
- ) ;
152
+ let ( width, height, output_buffer) = download_to_buffer ( device, & mut encoder, texture) ;
120
153
121
154
buffers_and_info. push ( ( output_buffer, width, height, * row. transform , * row. alpha_blending , * row. source_node_id ) ) ;
122
155
}
123
156
124
- // Submit all copy operations in a single batch
125
157
queue. submit ( [ encoder. finish ( ) ] ) ;
126
158
127
- // Now async map all buffers and collect futures
128
159
let mut map_futures = Vec :: new ( ) ;
129
160
for ( buffer, _width, _height, _transform, _alpha_blending, _source_node_id) in & buffers_and_info {
130
161
let buffer_slice = buffer. slice ( ..) ;
@@ -135,25 +166,19 @@ impl<'i> Convert<Table<Raster<CPU>>, &'i WgpuExecutor> for Table<Raster<GPU>> {
135
166
map_futures. push ( receiver) ;
136
167
}
137
168
138
- // Wait for all mapping operations to complete
139
- let map_results = futures:: future:: try_join_all ( map_futures) . await . map_err ( |_| "Failed to receive map result" ) . unwrap ( ) ;
169
+ let map_results = futures:: future:: try_join_all ( map_futures)
170
+ . await
171
+ . map_err ( |_| "Failed to receive map result" )
172
+ . expect ( "Buffer mapping communication failed" ) ;
140
173
141
- // Process all mapped buffers
142
174
let mut table = Vec :: new ( ) ;
143
175
for ( i, ( buffer, width, height, transform, alpha_blending, source_node_id) ) in buffers_and_info. into_iter ( ) . enumerate ( ) {
144
176
if let Err ( e) = & map_results[ i] {
145
- panic ! ( "Buffer mapping failed: {:?}" , e ) ;
177
+ panic ! ( "Buffer mapping failed: {e :?}" ) ;
146
178
}
147
179
148
180
let data = buffer. slice ( ..) . get_mapped_range ( ) ;
149
- // Convert bytes directly to Color via SRGBA8
150
- let cpu_data: Vec < Color > = data
151
- . chunks_exact ( 4 )
152
- . map ( |chunk| {
153
- // Create SRGBA8 from bytes, then convert to Color
154
- Color :: from_rgba8_srgb ( chunk[ 0 ] , chunk[ 1 ] , chunk[ 2 ] , chunk[ 3 ] )
155
- } )
156
- . collect ( ) ;
181
+ let cpu_data: Vec < Color > = data. chunks_exact ( 4 ) . map ( |chunk| Color :: from_rgba8_srgb ( chunk[ 0 ] , chunk[ 1 ] , chunk[ 2 ] , chunk[ 3 ] ) ) . collect ( ) ;
157
182
158
183
drop ( data) ;
159
184
buffer. unmap ( ) ;
@@ -177,6 +202,51 @@ impl<'i> Convert<Table<Raster<CPU>>, &'i WgpuExecutor> for Table<Raster<GPU>> {
177
202
}
178
203
}
179
204
205
+ /// Converts single GPU raster to CPU by downloading texture data
206
+ impl < ' i > Convert < Raster < CPU > , & ' i WgpuExecutor > for Raster < GPU > {
207
+ async fn convert ( self , _: Footprint , executor : & ' i WgpuExecutor ) -> Raster < CPU > {
208
+ let device = & executor. context . device ;
209
+ let queue = & executor. context . queue ;
210
+
211
+ let mut encoder = device. create_command_encoder ( & wgpu:: CommandEncoderDescriptor {
212
+ label : Some ( "single_texture_download_encoder" ) ,
213
+ } ) ;
214
+
215
+ let gpu_raster = & self ;
216
+ let texture = gpu_raster. data ( ) ;
217
+
218
+ let ( width, height, output_buffer) = download_to_buffer ( device, & mut encoder, texture) ;
219
+
220
+ queue. submit ( [ encoder. finish ( ) ] ) ;
221
+
222
+ let buffer_slice = output_buffer. slice ( ..) ;
223
+ let ( sender, receiver) = futures:: channel:: oneshot:: channel ( ) ;
224
+ buffer_slice. map_async ( wgpu:: MapMode :: Read , move |result| {
225
+ let _ = sender. send ( result) ;
226
+ } ) ;
227
+ receiver. await . expect ( "Failed to receive map result" ) . expect ( "Buffer mapping failed" ) ;
228
+
229
+ let data = output_buffer. slice ( ..) . get_mapped_range ( ) ;
230
+ let cpu_data: Vec < Color > = data. chunks_exact ( 4 ) . map ( |chunk| Color :: from_rgba8_srgb ( chunk[ 0 ] , chunk[ 1 ] , chunk[ 2 ] , chunk[ 3 ] ) ) . collect ( ) ;
231
+
232
+ drop ( data) ;
233
+ output_buffer. unmap ( ) ;
234
+ let cpu_image = Image {
235
+ data : cpu_data,
236
+ width,
237
+ height,
238
+ base64_string : None ,
239
+ } ;
240
+
241
+ Raster :: new_cpu ( cpu_image)
242
+ }
243
+ }
244
+
245
+ /// Node for uploading textures from CPU to GPU. This Is now deprecated and
246
+ /// we should use the Convert node in the future.
247
+ ///
248
+ /// Accepts either individual rasters or tables of rasters and converts them
249
+ /// to GPU format using the WgpuExecutor's device and queue.
180
250
#[ node_macro:: node( category( "" ) ) ]
181
251
pub async fn upload_texture < ' a : ' n , T : Convert < Table < Raster < GPU > > , & ' a WgpuExecutor > > (
182
252
_: impl Ctx ,
0 commit comments