OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "gpu/command_buffer/service/sync_point_manager.h" | 5 #include "gpu/command_buffer/service/sync_point_manager.h" |
6 | 6 |
7 #include <limits.h> | 7 #include <limits.h> |
8 #include <stddef.h> | 8 #include <stddef.h> |
9 #include <stdint.h> | 9 #include <stdint.h> |
10 | 10 |
11 #include <climits> | |
12 | |
13 #include "base/bind.h" | 11 #include "base/bind.h" |
14 #include "base/containers/hash_tables.h" | |
15 #include "base/location.h" | 12 #include "base/location.h" |
16 #include "base/logging.h" | 13 #include "base/logging.h" |
17 #include "base/memory/ptr_util.h" | 14 #include "base/memory/ref_counted.h" |
18 #include "base/rand_util.h" | |
19 #include "base/sequence_checker.h" | |
20 #include "base/single_thread_task_runner.h" | 15 #include "base/single_thread_task_runner.h" |
21 | 16 |
22 namespace gpu { | 17 namespace gpu { |
23 | 18 |
24 namespace { | 19 namespace { |
25 | 20 |
26 void RunOnThread(scoped_refptr<base::SingleThreadTaskRunner> task_runner, | 21 void RunOnThread(scoped_refptr<base::SingleThreadTaskRunner> task_runner, |
27 const base::Closure& callback) { | 22 const base::Closure& callback) { |
28 if (task_runner->BelongsToCurrentThread()) { | 23 if (task_runner->BelongsToCurrentThread()) { |
29 callback.Run(); | 24 callback.Run(); |
(...skipping 109 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
139 scoped_refptr<SyncPointClientState> state) | 134 scoped_refptr<SyncPointClientState> state) |
140 : order_num(order), | 135 : order_num(order), |
141 fence_release(release), | 136 fence_release(release), |
142 release_callback(callback), | 137 release_callback(callback), |
143 client_state(state) {} | 138 client_state(state) {} |
144 | 139 |
145 SyncPointOrderData::OrderFence::OrderFence(const OrderFence& other) = default; | 140 SyncPointOrderData::OrderFence::OrderFence(const OrderFence& other) = default; |
146 | 141 |
147 SyncPointOrderData::OrderFence::~OrderFence() {} | 142 SyncPointOrderData::OrderFence::~OrderFence() {} |
148 | 143 |
149 SyncPointOrderData::SyncPointOrderData() | 144 SyncPointOrderData::SyncPointOrderData() {} |
150 : current_order_num_(0), | |
151 paused_(false), | |
152 destroyed_(false), | |
153 processed_order_num_(0), | |
154 unprocessed_order_num_(0) {} | |
155 | 145 |
156 SyncPointOrderData::~SyncPointOrderData() {} | 146 SyncPointOrderData::~SyncPointOrderData() {} |
157 | 147 |
158 bool SyncPointOrderData::ValidateReleaseOrderNumber( | 148 bool SyncPointOrderData::ValidateReleaseOrderNumber( |
159 scoped_refptr<SyncPointClientState> client_state, | 149 scoped_refptr<SyncPointClientState> client_state, |
160 uint32_t wait_order_num, | 150 uint32_t wait_order_num, |
161 uint64_t fence_release, | 151 uint64_t fence_release, |
162 const base::Closure& release_callback) { | 152 const base::Closure& release_callback) { |
163 base::AutoLock auto_lock(lock_); | 153 base::AutoLock auto_lock(lock_); |
164 if (destroyed_) | 154 if (destroyed_) |
165 return false; | 155 return false; |
166 | 156 |
167 // Release should have a possible unprocessed order number lower | 157 // Release should have a possible unprocessed order number lower than the wait |
168 // than the wait order number. | 158 // order number. |
169 if ((processed_order_num_ + 1) >= wait_order_num) | 159 if ((processed_order_num_ + 1) >= wait_order_num) |
170 return false; | 160 return false; |
171 | 161 |
172 // Release should have more unprocessed numbers if we are waiting. | 162 // Release should have more unprocessed numbers if we are waiting. |
173 if (unprocessed_order_num_ <= processed_order_num_) | 163 if (unprocessed_order_num_ <= processed_order_num_) |
174 return false; | 164 return false; |
175 | 165 |
176 // So far it could be valid, but add an order fence guard to be sure it | 166 // So far it could be valid, but add an order fence guard to be sure it |
177 // gets released eventually. | 167 // gets released eventually. |
178 const uint32_t expected_order_num = | 168 uint32_t expected_order_num = |
179 std::min(unprocessed_order_num_, wait_order_num); | 169 std::min(unprocessed_order_num_, wait_order_num); |
180 order_fence_queue_.push(OrderFence(expected_order_num, fence_release, | 170 order_fence_queue_.push(OrderFence(expected_order_num, fence_release, |
181 release_callback, client_state)); | 171 release_callback, client_state)); |
182 return true; | 172 return true; |
183 } | 173 } |
184 | 174 |
185 SyncPointClientState::ReleaseCallback::ReleaseCallback( | 175 SyncPointClientState::ReleaseCallback::ReleaseCallback( |
186 uint64_t release, | 176 uint64_t release, |
187 const base::Closure& callback) | 177 const base::Closure& callback) |
188 : release_count(release), callback_closure(callback) {} | 178 : release_count(release), callback_closure(callback) {} |
189 | 179 |
190 SyncPointClientState::ReleaseCallback::ReleaseCallback( | 180 SyncPointClientState::ReleaseCallback::ReleaseCallback( |
191 const ReleaseCallback& other) = default; | 181 const ReleaseCallback& other) = default; |
192 | 182 |
193 SyncPointClientState::ReleaseCallback::~ReleaseCallback() {} | 183 SyncPointClientState::ReleaseCallback::~ReleaseCallback() {} |
194 | 184 |
195 SyncPointClientState::SyncPointClientState( | 185 SyncPointClientState::SyncPointClientState( |
196 scoped_refptr<SyncPointOrderData> order_data) | 186 scoped_refptr<SyncPointOrderData> order_data) |
197 : order_data_(order_data), fence_sync_release_(0) {} | 187 : order_data_(order_data) {} |
198 | 188 |
199 SyncPointClientState::~SyncPointClientState() { | 189 SyncPointClientState::~SyncPointClientState() {} |
| 190 |
| 191 bool SyncPointClientState::IsFenceSyncReleased(uint64_t release) { |
| 192 base::AutoLock lock(fence_sync_lock_); |
| 193 return release <= fence_sync_release_; |
200 } | 194 } |
201 | 195 |
202 bool SyncPointClientState::WaitForRelease(CommandBufferNamespace namespace_id, | 196 bool SyncPointClientState::WaitForRelease(uint64_t release, |
203 CommandBufferId client_id, | |
204 uint32_t wait_order_num, | 197 uint32_t wait_order_num, |
205 uint64_t release, | |
206 const base::Closure& callback) { | 198 const base::Closure& callback) { |
207 // Lock must be held the whole time while we validate otherwise it could be | 199 // Lock must be held the whole time while we validate otherwise it could be |
208 // released while we are checking. | 200 // released while we are checking. |
209 { | 201 { |
210 base::AutoLock auto_lock(fence_sync_lock_); | 202 base::AutoLock auto_lock(fence_sync_lock_); |
211 if (release > fence_sync_release_) { | 203 if (release > fence_sync_release_ && |
212 if (!order_data_->ValidateReleaseOrderNumber(this, wait_order_num, | 204 order_data_->ValidateReleaseOrderNumber(this, wait_order_num, release, |
213 release, callback)) { | 205 callback)) { |
214 return false; | 206 // Add the callback which will be called upon release. |
215 } else { | 207 release_callback_queue_.push(ReleaseCallback(release, callback)); |
216 // Add the callback which will be called upon release. | 208 return true; |
217 release_callback_queue_.push(ReleaseCallback(release, callback)); | |
218 if (!on_wait_callback_.is_null()) | |
219 on_wait_callback_.Run(namespace_id, client_id); | |
220 return true; | |
221 } | |
222 } | 209 } |
223 } | 210 } |
224 | 211 // Already released, do not run the callback. |
225 // Already released, run the callback now. | 212 return false; |
226 callback.Run(); | |
227 return true; | |
228 } | 213 } |
229 | 214 |
230 void SyncPointClientState::ReleaseFenceSync(uint64_t release) { | 215 void SyncPointClientState::ReleaseFenceSync(uint64_t release) { |
231 // Call callbacks without the lock to avoid possible deadlocks. | 216 // Call callbacks without the lock to avoid possible deadlocks. |
232 std::vector<base::Closure> callback_list; | 217 std::vector<base::Closure> callback_list; |
233 { | 218 { |
234 base::AutoLock auto_lock(fence_sync_lock_); | 219 base::AutoLock auto_lock(fence_sync_lock_); |
| 220 |
235 DLOG_IF(ERROR, release <= fence_sync_release_) | 221 DLOG_IF(ERROR, release <= fence_sync_release_) |
236 << "Client submitted fence releases out of order."; | 222 << "Client submitted fence releases out of order."; |
| 223 fence_sync_release_ = release; |
237 | 224 |
238 fence_sync_release_ = release; | |
239 while (!release_callback_queue_.empty() && | 225 while (!release_callback_queue_.empty() && |
240 release_callback_queue_.top().release_count <= release) { | 226 release_callback_queue_.top().release_count <= release) { |
241 callback_list.push_back(release_callback_queue_.top().callback_closure); | 227 callback_list.push_back(release_callback_queue_.top().callback_closure); |
242 release_callback_queue_.pop(); | 228 release_callback_queue_.pop(); |
243 } | 229 } |
244 } | 230 } |
245 | 231 |
246 for (const base::Closure& closure : callback_list) { | 232 for (const base::Closure& closure : callback_list) { |
247 closure.Run(); | 233 closure.Run(); |
248 } | 234 } |
(...skipping 30 matching lines...) Expand all Loading... |
279 release_callback_queue_.push(popped_callback); | 265 release_callback_queue_.push(popped_callback); |
280 } | 266 } |
281 } | 267 } |
282 | 268 |
283 if (call_callback) { | 269 if (call_callback) { |
284 // This effectively releases the wait without releasing the fence. | 270 // This effectively releases the wait without releasing the fence. |
285 callback.Run(); | 271 callback.Run(); |
286 } | 272 } |
287 } | 273 } |
288 | 274 |
289 void SyncPointClientState::SetOnWaitCallback(const OnWaitCallback& callback) { | 275 SyncPointClient::SyncPointClient(SyncPointManager* sync_point_manager, |
290 on_wait_callback_ = callback; | 276 scoped_refptr<SyncPointOrderData> order_data, |
| 277 CommandBufferNamespace namespace_id, |
| 278 CommandBufferId command_buffer_id) |
| 279 : sync_point_manager_(sync_point_manager), |
| 280 order_data_(order_data), |
| 281 client_state_(new SyncPointClientState(order_data)), |
| 282 namespace_id_(namespace_id), |
| 283 command_buffer_id_(command_buffer_id) { |
| 284 sync_point_manager_->RegisterSyncPointClient(client_state_, namespace_id, |
| 285 command_buffer_id); |
291 } | 286 } |
292 | 287 |
293 SyncPointClient::~SyncPointClient() { | 288 SyncPointClient::~SyncPointClient() { |
294 if (namespace_id_ != gpu::CommandBufferNamespace::INVALID) { | 289 // Release all fences on destruction. |
295 // Release all fences on destruction. | 290 client_state_->ReleaseFenceSync(UINT64_MAX); |
296 client_state_->ReleaseFenceSync(UINT64_MAX); | 291 sync_point_manager_->DeregisterSyncPointClient(namespace_id_, |
297 | 292 command_buffer_id_); |
298 sync_point_manager_->DestroySyncPointClient(namespace_id_, client_id_); | |
299 } | |
300 } | 293 } |
301 | 294 |
302 bool SyncPointClient::Wait(SyncPointClientState* release_state, | 295 bool SyncPointClient::Wait(const SyncToken& sync_token, |
303 uint64_t release_count, | 296 const base::Closure& callback) { |
304 const base::Closure& wait_complete_callback) { | |
305 // Validate that this Wait call is between BeginProcessingOrderNumber() and | 297 // Validate that this Wait call is between BeginProcessingOrderNumber() and |
306 // FinishProcessingOrderNumber(), or else we may deadlock. | 298 // FinishProcessingOrderNumber(), or else we may deadlock. |
307 DCHECK(client_state_->order_data()->IsProcessingOrderNumber()); | 299 DCHECK(order_data_->IsProcessingOrderNumber()); |
308 | 300 if (sync_token.namespace_id() == namespace_id_ && |
309 const uint32_t wait_order_number = | 301 sync_token.command_buffer_id() == command_buffer_id_) { |
310 client_state_->order_data()->current_order_num(); | |
311 | |
312 // If waiting on self or wait was invalid, call the callback and return false. | |
313 if (client_state_ == release_state || | |
314 !release_state->WaitForRelease(namespace_id_, client_id_, | |
315 wait_order_number, release_count, | |
316 wait_complete_callback)) { | |
317 wait_complete_callback.Run(); | |
318 return false; | 302 return false; |
319 } | 303 } |
320 return true; | 304 uint32_t wait_order_number = order_data_->current_order_num(); |
| 305 return sync_point_manager_->Wait(sync_token, wait_order_number, callback); |
321 } | 306 } |
322 | 307 |
323 bool SyncPointClient::WaitNonThreadSafe( | 308 bool SyncPointClient::WaitNonThreadSafe( |
324 SyncPointClientState* release_state, | 309 const SyncToken& sync_token, |
325 uint64_t release_count, | 310 scoped_refptr<base::SingleThreadTaskRunner> task_runner, |
326 scoped_refptr<base::SingleThreadTaskRunner> runner, | 311 const base::Closure& callback) { |
327 const base::Closure& wait_complete_callback) { | 312 return Wait(sync_token, base::Bind(&RunOnThread, task_runner, callback)); |
328 return Wait(release_state, release_count, | |
329 base::Bind(&RunOnThread, runner, wait_complete_callback)); | |
330 } | |
331 | |
332 bool SyncPointClient::WaitOutOfOrder( | |
333 SyncPointClientState* release_state, | |
334 uint64_t release_count, | |
335 const base::Closure& wait_complete_callback) { | |
336 // Validate that this Wait call is not between BeginProcessingOrderNumber() | |
337 // and FinishProcessingOrderNumber(), or else we may deadlock. | |
338 DCHECK(!client_state_ || | |
339 !client_state_->order_data()->IsProcessingOrderNumber()); | |
340 | |
341 // No order number associated with the current execution context, using | |
342 // UINT32_MAX will just assume the release is in the SyncPointClientState's | |
343 // order numbers to be executed. | |
344 if (!release_state->WaitForRelease(namespace_id_, client_id_, UINT32_MAX, | |
345 release_count, wait_complete_callback)) { | |
346 wait_complete_callback.Run(); | |
347 return false; | |
348 } | |
349 return true; | |
350 } | |
351 | |
352 bool SyncPointClient::WaitOutOfOrderNonThreadSafe( | |
353 SyncPointClientState* release_state, | |
354 uint64_t release_count, | |
355 scoped_refptr<base::SingleThreadTaskRunner> runner, | |
356 const base::Closure& wait_complete_callback) { | |
357 return WaitOutOfOrder( | |
358 release_state, release_count, | |
359 base::Bind(&RunOnThread, runner, wait_complete_callback)); | |
360 } | 313 } |
361 | 314 |
362 void SyncPointClient::ReleaseFenceSync(uint64_t release) { | 315 void SyncPointClient::ReleaseFenceSync(uint64_t release) { |
363 // Validate that this Release call is between BeginProcessingOrderNumber() and | 316 // Validate that this Release call is between BeginProcessingOrderNumber() and |
364 // FinishProcessingOrderNumber(), or else we may deadlock. | 317 // FinishProcessingOrderNumber(), or else we may deadlock. |
365 DCHECK(client_state_->order_data()->IsProcessingOrderNumber()); | 318 DCHECK(order_data_->IsProcessingOrderNumber()); |
366 client_state_->ReleaseFenceSync(release); | 319 client_state_->ReleaseFenceSync(release); |
367 } | 320 } |
368 | 321 |
369 void SyncPointClient::SetOnWaitCallback(const OnWaitCallback& callback) { | 322 SyncPointManager::SyncPointManager() { |
370 client_state_->SetOnWaitCallback(callback); | |
371 } | |
372 | |
373 SyncPointClient::SyncPointClient() | |
374 : sync_point_manager_(nullptr), | |
375 namespace_id_(gpu::CommandBufferNamespace::INVALID), | |
376 client_id_() {} | |
377 | |
378 SyncPointClient::SyncPointClient(SyncPointManager* sync_point_manager, | |
379 scoped_refptr<SyncPointOrderData> order_data, | |
380 CommandBufferNamespace namespace_id, | |
381 CommandBufferId client_id) | |
382 : sync_point_manager_(sync_point_manager), | |
383 client_state_(new SyncPointClientState(order_data)), | |
384 namespace_id_(namespace_id), | |
385 client_id_(client_id) {} | |
386 | |
387 SyncPointManager::SyncPointManager(bool allow_threaded_wait) { | |
388 global_order_num_.GetNext(); | 323 global_order_num_.GetNext(); |
389 } | 324 } |
390 | 325 |
391 SyncPointManager::~SyncPointManager() { | 326 SyncPointManager::~SyncPointManager() { |
392 for (const ClientMap& client_map : client_maps_) { | 327 for (const ClientStateMap& client_state_map : client_state_maps_) |
393 DCHECK(client_map.empty()); | 328 DCHECK(client_state_map.empty()); |
394 } | |
395 } | 329 } |
396 | 330 |
397 std::unique_ptr<SyncPointClient> SyncPointManager::CreateSyncPointClient( | 331 bool SyncPointManager::IsSyncTokenReleased(const SyncToken& sync_token) { |
398 scoped_refptr<SyncPointOrderData> order_data, | 332 scoped_refptr<SyncPointClientState> release_state = GetSyncPointClientState( |
399 CommandBufferNamespace namespace_id, | 333 sync_token.namespace_id(), sync_token.command_buffer_id()); |
400 CommandBufferId client_id) { | 334 if (release_state) |
401 DCHECK_GE(namespace_id, 0); | 335 return release_state->IsFenceSyncReleased(sync_token.release_count()); |
402 DCHECK_LT(static_cast<size_t>(namespace_id), arraysize(client_maps_)); | 336 return true; |
403 base::AutoLock auto_lock(client_maps_lock_); | |
404 | |
405 ClientMap& client_map = client_maps_[namespace_id]; | |
406 std::pair<ClientMap::iterator, bool> result = client_map.insert( | |
407 std::make_pair(client_id, new SyncPointClient(this, order_data, | |
408 namespace_id, client_id))); | |
409 DCHECK(result.second); | |
410 | |
411 return base::WrapUnique(result.first->second); | |
412 } | 337 } |
413 | 338 |
414 std::unique_ptr<SyncPointClient> | 339 bool SyncPointManager::Wait(const SyncToken& sync_token, |
415 SyncPointManager::CreateSyncPointClientWaiter() { | 340 uint32_t wait_order_num, |
416 return base::WrapUnique(new SyncPointClient); | 341 const base::Closure& callback) { |
| 342 scoped_refptr<SyncPointClientState> release_state = GetSyncPointClientState( |
| 343 sync_token.namespace_id(), sync_token.command_buffer_id()); |
| 344 if (release_state && |
| 345 release_state->WaitForRelease(sync_token.release_count(), wait_order_num, |
| 346 callback)) { |
| 347 return true; |
| 348 } |
| 349 // Do not run callback if wait is invalid. |
| 350 return false; |
417 } | 351 } |
418 | 352 |
419 scoped_refptr<SyncPointClientState> SyncPointManager::GetSyncPointClientState( | 353 bool SyncPointManager::WaitNonThreadSafe( |
| 354 const SyncToken& sync_token, |
| 355 uint32_t wait_order_num, |
| 356 scoped_refptr<base::SingleThreadTaskRunner> task_runner, |
| 357 const base::Closure& callback) { |
| 358 return Wait(sync_token, wait_order_num, |
| 359 base::Bind(&RunOnThread, task_runner, callback)); |
| 360 } |
| 361 |
| 362 bool SyncPointManager::WaitOutOfOrder(const SyncToken& trusted_sync_token, |
| 363 const base::Closure& callback) { |
| 364 // No order number associated with the current execution context, using |
| 365 // UINT32_MAX will just assume the release is in the SyncPointClientState's |
| 366 // order numbers to be executed. |
| 367 return Wait(trusted_sync_token, UINT32_MAX, callback); |
| 368 } |
| 369 |
| 370 bool SyncPointManager::WaitOutOfOrderNonThreadSafe( |
| 371 const SyncToken& trusted_sync_token, |
| 372 scoped_refptr<base::SingleThreadTaskRunner> task_runner, |
| 373 const base::Closure& callback) { |
| 374 return WaitOutOfOrder(trusted_sync_token, |
| 375 base::Bind(&RunOnThread, task_runner, callback)); |
| 376 } |
| 377 |
| 378 void SyncPointManager::RegisterSyncPointClient( |
| 379 scoped_refptr<SyncPointClientState> client_state, |
420 CommandBufferNamespace namespace_id, | 380 CommandBufferNamespace namespace_id, |
421 CommandBufferId client_id) { | 381 CommandBufferId command_buffer_id) { |
422 if (namespace_id >= 0) { | 382 DCHECK_GE(namespace_id, 0); |
423 DCHECK_LT(static_cast<size_t>(namespace_id), arraysize(client_maps_)); | 383 DCHECK_LT(static_cast<size_t>(namespace_id), arraysize(client_state_maps_)); |
424 base::AutoLock auto_lock(client_maps_lock_); | 384 |
425 ClientMap& client_map = client_maps_[namespace_id]; | 385 base::AutoLock auto_lock(client_state_maps_lock_); |
426 ClientMap::iterator it = client_map.find(client_id); | 386 DCHECK(!client_state_maps_[namespace_id].count(command_buffer_id)); |
427 if (it != client_map.end()) { | 387 client_state_maps_[namespace_id].insert( |
428 return it->second->client_state(); | 388 std::make_pair(command_buffer_id, client_state)); |
429 } | 389 } |
430 } | 390 |
431 return nullptr; | 391 void SyncPointManager::DeregisterSyncPointClient( |
| 392 CommandBufferNamespace namespace_id, |
| 393 CommandBufferId command_buffer_id) { |
| 394 DCHECK_GE(namespace_id, 0); |
| 395 DCHECK_LT(static_cast<size_t>(namespace_id), arraysize(client_state_maps_)); |
| 396 |
| 397 base::AutoLock auto_lock(client_state_maps_lock_); |
| 398 DCHECK(client_state_maps_[namespace_id].count(command_buffer_id)); |
| 399 client_state_maps_[namespace_id].erase(command_buffer_id); |
432 } | 400 } |
433 | 401 |
434 uint32_t SyncPointManager::GenerateOrderNumber() { | 402 uint32_t SyncPointManager::GenerateOrderNumber() { |
435 return global_order_num_.GetNext(); | 403 return global_order_num_.GetNext(); |
436 } | 404 } |
437 | 405 |
438 void SyncPointManager::DestroySyncPointClient( | 406 scoped_refptr<SyncPointClientState> SyncPointManager::GetSyncPointClientState( |
439 CommandBufferNamespace namespace_id, | 407 CommandBufferNamespace namespace_id, |
440 CommandBufferId client_id) { | 408 CommandBufferId command_buffer_id) { |
441 DCHECK_GE(namespace_id, 0); | 409 if (namespace_id >= 0) { |
442 DCHECK_LT(static_cast<size_t>(namespace_id), arraysize(client_maps_)); | 410 DCHECK_LT(static_cast<size_t>(namespace_id), arraysize(client_state_maps_)); |
443 | 411 base::AutoLock auto_lock(client_state_maps_lock_); |
444 base::AutoLock auto_lock(client_maps_lock_); | 412 ClientStateMap& client_state_map = client_state_maps_[namespace_id]; |
445 ClientMap& client_map = client_maps_[namespace_id]; | 413 auto it = client_state_map.find(command_buffer_id); |
446 ClientMap::iterator it = client_map.find(client_id); | 414 if (it != client_state_map.end()) |
447 DCHECK(it != client_map.end()); | 415 return it->second; |
448 client_map.erase(it); | 416 } |
| 417 return nullptr; |
449 } | 418 } |
450 | 419 |
451 } // namespace gpu | 420 } // namespace gpu |
OLD | NEW |