Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions core/src/avm2/object/context3d_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,7 @@ impl<'gc> Context3DObject<'gc> {
source_height: source.height(),
dest,
layer,
needs_conversion: true,
})
});
}
Expand All @@ -378,6 +379,7 @@ impl<'gc> Context3DObject<'gc> {
source_height: dest.height(),
dest,
layer,
needs_conversion: false,
})
});
}
Expand Down
3 changes: 3 additions & 0 deletions render/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,9 @@ pub enum Context3DCommand<'a> {
source_height: u32,
dest: Rc<dyn Texture>,
layer: u32,
/// If true, source is raw RGBA8 data that may need format conversion.
/// If false, source is already in the destination format (e.g., pre-compressed).
needs_conversion: bool,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's non-intuitive and closed for extension.

Have you considered using render's Bitmap<'a>?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't. I'll take a stab at it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You want me to add more formats to to the BitmapFormat enum?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, if we're using other formats it makes sense to add them there and provide conversions

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok that confuses me a little bit. Because there are only 2 options here. Either the data is rgba (and needs conversion), or it's already in the dst texture format. So it doesn't really make sense to me to but a Bitmap<'a> here, that just allows for more options that are impossible to happen.

},
SetTextureAt {
sampler: u32,
Expand Down
2 changes: 2 additions & 0 deletions render/wgpu/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ naga-pixelbender = { path = "../naga-pixelbender" }
profiling = { version = "1.0", default-features = false, optional = true }
lru = "0.16.2"
naga = { workspace = true }
texpresso = "2.0.2"
half = "2.7"
indexmap = { workspace = true }
smallvec = { workspace = true }

Expand Down
52 changes: 49 additions & 3 deletions render/wgpu/src/context3d/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use ruffle_render::bitmap::BitmapHandle;
use ruffle_render::error::Error;
use std::any::Any;
use std::borrow::Cow;
use std::cell::Cell;
use swf::{Rectangle, Twips};

Expand Down Expand Up @@ -966,6 +967,7 @@
source_height,
dest,
layer,
needs_conversion,
} => {
let dest = Rc::<dyn Any>::downcast::<TextureWrapper>(dest).unwrap();

Expand All @@ -975,6 +977,49 @@
// BitmapData's gpu texture might be modified before we actually submit
// `buffer_command_encoder` to the device.
let dest_format = dest.texture.format();

// If needs_conversion is true, source is raw RGBA8 data that may need
// format conversion. Otherwise, source is already in the destination format.
let upload_source: Cow<'_, [u8]> = if needs_conversion {
match dest_format {
wgpu::TextureFormat::Rgba8Unorm => Cow::Borrowed(source),
wgpu::TextureFormat::Rgba16Float => {
// Convert RGBA8 (0-255) to RGBA16Float (0.0-1.0)
let converted: Vec<u8> = source
.iter()
.flat_map(|&byte| {
half::f16::from_f32(byte as f32 / 255.0).to_le_bytes()
})
.collect();
Cow::Owned(converted)
}
wgpu::TextureFormat::Bc3RgbaUnorm => {
// Compress RGBA8 to BC3/DXT5
let format = texpresso::Format::Bc3;
let compressed_size = format
.compressed_size(source_width as usize, source_height as usize);
let mut buffer = vec![0u8; compressed_size];
format.compress(
source,
source_width as usize,
source_height as usize,
texpresso::Params::default(),
&mut buffer,
);
Cow::Owned(buffer)
}
_ => {
tracing::warn!(

Check warning on line 1012 in render/wgpu/src/context3d/mod.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered line (1012)
"Unhandled texture format conversion: {:?}",
dest_format
);
Cow::Borrowed(source)

Check warning on line 1016 in render/wgpu/src/context3d/mod.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered line (1016)
}
}
} else {
Cow::Borrowed(source)
};

let src_bytes_per_row = dest_format.block_copy_size(None).unwrap()
* (source_width / dest_format.block_dimensions().0);

Expand All @@ -989,7 +1034,8 @@
};
let dest_size = dest_bytes_per_row as u64 * rows_per_image as u64;
assert!(
dest_bytes_per_row >= src_bytes_per_row && dest_size >= source.len() as u64
dest_bytes_per_row >= src_bytes_per_row
&& dest_size >= upload_source.len() as u64
);

let texture_buffer = self.descriptors.device.create_buffer(&BufferDescriptor {
Expand All @@ -1002,12 +1048,12 @@
let mut texture_buffer_view = texture_buffer.slice(..).get_mapped_range_mut();
if dest_bytes_per_row == src_bytes_per_row {
// No padding, we can copy everything in one go.
texture_buffer_view.copy_from_slice(source);
texture_buffer_view.copy_from_slice(&upload_source);
} else {
// Copy row by row.
for (dest, src) in texture_buffer_view
.chunks_exact_mut(dest_bytes_per_row as usize)
.zip(source.chunks_exact(src_bytes_per_row as usize))
.zip(upload_source.chunks_exact(src_bytes_per_row as usize))
{
let (dest, padding) = dest.split_at_mut(src_bytes_per_row as usize);
dest.copy_from_slice(src);
Expand Down
117 changes: 117 additions & 0 deletions tests/tests/swfs/avm2/stage3d_texture_format_conversion/Test.as
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package {
import com.adobe.utils.AGALMiniAssembler;

import flash.display.Stage3D;
import flash.display3D.Context3D;
import flash.display3D.Context3DBlendFactor;
import flash.display3D.Context3DProgramType;
import flash.display3D.Context3DRenderMode;
import flash.display3D.Context3DVertexBufferFormat;
import flash.display3D.IndexBuffer3D;
import flash.display3D.Program3D;
import flash.display3D.VertexBuffer3D;
import flash.events.Event;
import flash.display.MovieClip;
import flash.display.BitmapData;

public class Test extends MovieClip {
public const viewWidth:Number = 500;
public const viewHeight:Number = 500;

private var stage3D:Stage3D;
private var renderContext:Context3D;
private var indexList:IndexBuffer3D;
private var vertexes:VertexBuffer3D;

private const VERTEX_SHADER:String =
"add op, va0, vc0 \n" +
"mov v0, va1";

private const FRAGMENT_SHADER_DXT5:String =
"tex oc, v0, fs0 <2d,clamp,linear,mipnone,dxt5>";

private const FRAGMENT_SHADER_FLOAT:String =
"tex oc, v0, fs0 <2d,clamp,linear,mipnone>";

private var vertexAssembly:AGALMiniAssembler = new AGALMiniAssembler(false);
private var fragmentAssemblyDxt5:AGALMiniAssembler = new AGALMiniAssembler(false);
private var fragmentAssemblyFloat:AGALMiniAssembler = new AGALMiniAssembler(false);
private var programDxt5:Program3D;
private var programFloat:Program3D;

[Embed(source = "circle.png")]
public var CIRCLE_PNG: Class;

public function Test() {
stage3D = this.stage.stage3Ds[0];
stage3D.addEventListener(Event.CONTEXT3D_CREATE, contextCreated);
// Request "standard" profile which supports compressedAlpha and rgbaHalfFloat
stage3D.requestContext3D(Context3DRenderMode.AUTO, "standard");

vertexAssembly.assemble(Context3DProgramType.VERTEX, VERTEX_SHADER, 2);
fragmentAssemblyDxt5.assemble(Context3DProgramType.FRAGMENT, FRAGMENT_SHADER_DXT5, 2);
fragmentAssemblyFloat.assemble(Context3DProgramType.FRAGMENT, FRAGMENT_SHADER_FLOAT, 2);
}

private function contextCreated(event:Event):void {
renderContext = Stage3D(event.target).context3D;
renderContext.enableErrorChecking = true;
renderContext.configureBackBuffer(viewWidth, viewHeight, 4, true);

var triangles:Vector.<uint> = Vector.<uint>([0, 1, 2, 0, 2, 3]);
indexList = renderContext.createIndexBuffer(triangles.length);
indexList.uploadFromVector(triangles, 0, triangles.length);

const dataPerVertex:int = 5;
var vertexData:Vector.<Number> = Vector.<Number>([
-0.4, -0.4, 0, 0, 1,
0.4, -0.4, 0, 1, 1,
0.4, 0.4, 0, 1, 0,
-0.4, 0.4, 0, 0, 0
]);
vertexes = renderContext.createVertexBuffer(vertexData.length / dataPerVertex, dataPerVertex);
vertexes.uploadFromVector(vertexData, 0, vertexData.length / dataPerVertex);

renderContext.setVertexBufferAt(0, vertexes, 0, Context3DVertexBufferFormat.FLOAT_3);
renderContext.setVertexBufferAt(1, vertexes, 3, Context3DVertexBufferFormat.FLOAT_2);

var circleBitmap:BitmapData = new CIRCLE_PNG().bitmapData;

var compressedTexture = renderContext.createTexture(
512, 512,
"compressedAlpha",
false
);
compressedTexture.uploadFromBitmapData(circleBitmap);

var halfFloatTexture = renderContext.createTexture(
512, 512,
"rgbaHalfFloat",
false
);
halfFloatTexture.uploadFromBitmapData(circleBitmap);

programDxt5 = renderContext.createProgram();
programDxt5.upload(vertexAssembly.agalcode, fragmentAssemblyDxt5.agalcode);

programFloat = renderContext.createProgram();
programFloat.upload(vertexAssembly.agalcode, fragmentAssemblyFloat.agalcode);

renderContext.setBlendFactors(Context3DBlendFactor.SOURCE_ALPHA, Context3DBlendFactor.ONE_MINUS_SOURCE_ALPHA);

renderContext.clear(.3, .3, .3, 1, 1, 0);

renderContext.setProgram(programDxt5);
renderContext.setTextureAt(0, compressedTexture);
renderContext.setProgramConstantsFromVector("vertex", 0, Vector.<Number>([-0.5, 0.0, 0.0, 0.0]));
renderContext.drawTriangles(indexList, 0, 2);

renderContext.setProgram(programFloat);
renderContext.setTextureAt(0, halfFloatTexture);
renderContext.setProgramConstantsFromVector("vertex", 0, Vector.<Number>([0.5, 0.0, 0.0, 0.0]));
renderContext.drawTriangles(indexList, 0, 2);

renderContext.present();
}
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading