|
7 | 7 | from dataclasses import dataclass |
8 | 8 |
|
9 | 9 | import slangpy as spy |
10 | | -from slangpy import TextureLoader, Bitmap, Format, DataStruct, FormatSupport |
| 10 | +from slangpy import TextureLoader, Bitmap, Format, DataStruct, FormatSupport, FormatOverride |
11 | 11 | from slangpy.testing import helpers |
12 | 12 |
|
13 | 13 |
|
@@ -292,6 +292,106 @@ def test_load_textures(device_type: spy.DeviceType): |
292 | 292 | assert len(textures) == 2 |
293 | 293 |
|
294 | 294 |
|
| 295 | +@dataclass |
| 296 | +class FormatCallbackTestCase: |
| 297 | + component_type: ComponentType |
| 298 | + rg_format: Format |
| 299 | + y_value: float |
| 300 | + a_value: float |
| 301 | + dtype: npt.DTypeLike # type: ignore |
| 302 | + |
| 303 | + |
| 304 | +FORMAT_CALLBACK_TEST_CASES = [ |
| 305 | + FormatCallbackTestCase(ComponentType.uint8, Format.rg8_unorm, 128, 200, np.uint8), |
| 306 | + FormatCallbackTestCase(ComponentType.uint16, Format.rg16_unorm, 32768, 50000, np.uint16), |
| 307 | + FormatCallbackTestCase(ComponentType.float32, Format.rg32_float, 0.5, 0.8, np.float32), |
| 308 | +] |
| 309 | + |
| 310 | + |
| 311 | +@pytest.mark.parametrize("device_type", helpers.DEFAULT_DEVICE_TYPES) |
| 312 | +@pytest.mark.parametrize("test_case", FORMAT_CALLBACK_TEST_CASES) |
| 313 | +def test_format_callback_ya_to_rg(device_type: spy.DeviceType, test_case: FormatCallbackTestCase): |
| 314 | + """Test format_callback to load YA bitmap as RG texture instead of RGBA.""" |
| 315 | + device = helpers.get_device(type=device_type) |
| 316 | + |
| 317 | + # Check if the RG format is supported |
| 318 | + format_support = device.get_format_support(test_case.rg_format) |
| 319 | + if not FormatSupport.shader_load in format_support: |
| 320 | + pytest.skip(f"Format {test_case.rg_format} not supported as shader resource") |
| 321 | + |
| 322 | + # Create YA bitmap |
| 323 | + bitmap = Bitmap( |
| 324 | + pixel_format=PixelFormat.ya, |
| 325 | + component_type=test_case.component_type, |
| 326 | + width=100, |
| 327 | + height=50, |
| 328 | + ) |
| 329 | + |
| 330 | + # Fill with test data |
| 331 | + a = np.array(bitmap, copy=False) |
| 332 | + a[:, :, 0] = test_case.dtype(test_case.y_value) # Y (luminance) |
| 333 | + a[:, :, 1] = test_case.dtype(test_case.a_value) # A (alpha) |
| 334 | + |
| 335 | + # Define callback to keep YA as RG (Y->R, A->G) |
| 336 | + # Since YA and RG have the same memory layout (2 channels), we don't need |
| 337 | + # to convert - just upload the YA data directly as RG texture |
| 338 | + rg_format = test_case.rg_format |
| 339 | + |
| 340 | + def ya_to_rg_callback(device, bmp): |
| 341 | + if bmp.pixel_format == PixelFormat.ya: |
| 342 | + # Keep as RG without conversion: Y maps to R, A maps to G |
| 343 | + # convert_to=None means use bitmap data as-is |
| 344 | + return FormatOverride(format=rg_format, convert_to=None) |
| 345 | + return None |
| 346 | + |
| 347 | + # Load with callback |
| 348 | + loader = TextureLoader(device) |
| 349 | + options = TextureLoader.Options() |
| 350 | + options.load_as_normalized = True |
| 351 | + options.format_callback = ya_to_rg_callback |
| 352 | + texture = loader.load_texture(bitmap=bitmap, options=options) |
| 353 | + |
| 354 | + # Verify the texture is RG format, not RGBA |
| 355 | + assert texture.format == test_case.rg_format |
| 356 | + assert texture.width == bitmap.width |
| 357 | + assert texture.height == bitmap.height |
| 358 | + |
| 359 | + # Verify data: R should be Y, G should be A |
| 360 | + data = texture.to_numpy() |
| 361 | + assert data.shape == (50, 100, 2) |
| 362 | + assert np.allclose(data[:, :, 0], test_case.y_value, atol=1) # R = Y |
| 363 | + assert np.allclose(data[:, :, 1], test_case.a_value, atol=1) # G = A |
| 364 | + |
| 365 | + |
| 366 | +@pytest.mark.parametrize("device_type", helpers.DEFAULT_DEVICE_TYPES) |
| 367 | +def test_format_callback_default_behavior(device_type: spy.DeviceType): |
| 368 | + """Test that returning None from callback uses default behavior.""" |
| 369 | + device = helpers.get_device(type=device_type) |
| 370 | + |
| 371 | + # Create YA bitmap |
| 372 | + bitmap = Bitmap( |
| 373 | + pixel_format=PixelFormat.ya, |
| 374 | + component_type=ComponentType.uint8, |
| 375 | + width=100, |
| 376 | + height=50, |
| 377 | + ) |
| 378 | + |
| 379 | + # Callback that returns None (use default) |
| 380 | + def passthrough_callback(device, bmp): |
| 381 | + return None |
| 382 | + |
| 383 | + # Load with callback that returns None |
| 384 | + loader = TextureLoader(device) |
| 385 | + options = TextureLoader.Options() |
| 386 | + options.load_as_normalized = True |
| 387 | + options.load_as_srgb = False # Disable sRGB to get rgba8_unorm |
| 388 | + options.format_callback = passthrough_callback |
| 389 | + texture = loader.load_texture(bitmap=bitmap, options=options) |
| 390 | + |
| 391 | + # Should use default behavior: YA -> RGBA |
| 392 | + assert texture.format == Format.rgba8_unorm |
| 393 | + |
| 394 | + |
295 | 395 | @pytest.mark.parametrize("device_type", helpers.DEFAULT_DEVICE_TYPES) |
296 | 396 | def test_load_texture_array(device_type: spy.DeviceType): |
297 | 397 | device = helpers.get_device(type=device_type) |
|
0 commit comments