Merge pull request #384 from mahkoh/jorth/legacy-tex
vulkan: optimize draw calls
This commit is contained in:
commit
5d81d7609a
52 changed files with 1104 additions and 551 deletions
|
|
@ -15,16 +15,22 @@ pub fn main() -> anyhow::Result<()> {
|
|||
compile_simple("tex.frag")?;
|
||||
compile_simple("out.vert")?;
|
||||
compile_simple("out.frag")?;
|
||||
compile_simple("legacy/fill.frag")?;
|
||||
compile_simple("legacy/fill.vert")?;
|
||||
compile_simple("legacy/tex.vert")?;
|
||||
compile_simple("legacy/tex.frag")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compile_simple(name: &str) -> anyhow::Result<()> {
|
||||
compile_shader(name, &format!("{name}.spv"), None).with_context(|| name.to_string())
|
||||
let out = format!("{name}.spv").replace("/", "_");
|
||||
compile_shader(name, &out).with_context(|| name.to_string())
|
||||
}
|
||||
|
||||
fn compile_shader(name: &str, out: &str, options: Option<CompileOptions>) -> anyhow::Result<()> {
|
||||
let read = |path: &str| std::fs::read_to_string(format!("{}/{}", ROOT, path));
|
||||
let mut options = options.unwrap_or_else(|| CompileOptions::new().unwrap());
|
||||
fn compile_shader(name: &str, out: &str) -> anyhow::Result<()> {
|
||||
let root = Path::new(ROOT).join(Path::new(name).parent().unwrap());
|
||||
let read = |path: &str| std::fs::read_to_string(root.join(path));
|
||||
let mut options = CompileOptions::new().unwrap();
|
||||
options.set_include_callback(|name, _, _, _| {
|
||||
Ok(ResolvedInclude {
|
||||
resolved_name: name.to_string(),
|
||||
|
|
@ -40,7 +46,7 @@ fn compile_shader(name: &str, out: &str, options: Option<CompileOptions>) -> any
|
|||
"vert" => shaderc::ShaderKind::Vertex,
|
||||
n => bail!("Unknown shader stage {}", n),
|
||||
};
|
||||
let src = read(name)?;
|
||||
let src = std::fs::read_to_string(format!("{}/{}", ROOT, name))?;
|
||||
let compiler = shaderc::Compiler::new().unwrap();
|
||||
let binary = compiler
|
||||
.compile_into_spirv(&src, stage, name, "main", Some(&options))
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use {
|
||||
crate::{
|
||||
cli::{GlobalArgs, color::parse_color, duration::parse_duration},
|
||||
theme::TransferFunction,
|
||||
tools::tool_client::{ToolClient, with_tool_client},
|
||||
wire::jay_damage_tracking::{SetVisualizerColor, SetVisualizerDecay, SetVisualizerEnabled},
|
||||
},
|
||||
|
|
@ -85,12 +86,13 @@ impl DamageTracking {
|
|||
}
|
||||
DamageTrackingCmd::SetColor(c) => {
|
||||
let color = parse_color(&c.color);
|
||||
let [r, g, b, a] = color.to_array(TransferFunction::Srgb);
|
||||
tc.send(SetVisualizerColor {
|
||||
self_id: dt,
|
||||
r: color.r,
|
||||
g: color.g,
|
||||
b: color.b,
|
||||
a: color.a,
|
||||
r,
|
||||
g,
|
||||
b,
|
||||
a,
|
||||
});
|
||||
}
|
||||
DamageTrackingCmd::SetDecay(c) => {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ use {
|
|||
output_schedule::map_cursor_hz,
|
||||
scale::Scale,
|
||||
state::{ConnectorData, DeviceHandlerData, DrmDevData, OutputData, State},
|
||||
theme::{Color, ThemeSized},
|
||||
theme::{Color, ThemeSized, TransferFunction},
|
||||
tree::{
|
||||
ContainerNode, ContainerSplit, FloatNode, Node, NodeVisitorBase, OutputNode,
|
||||
TearingMode, VrrMode, WsMoveConfig, move_ws_to_output,
|
||||
|
|
@ -1599,8 +1599,8 @@ impl ConfigProxyHandler {
|
|||
|
||||
fn handle_get_color(&self, colorable: Colorable) -> Result<(), CphError> {
|
||||
let color = self.get_color(colorable)?.get();
|
||||
let color =
|
||||
jay_config::theme::Color::new_f32_premultiplied(color.r, color.g, color.b, color.a);
|
||||
let [r, g, b, a] = color.to_array(TransferFunction::Srgb);
|
||||
let color = jay_config::theme::Color::new_f32_premultiplied(r, g, b, a);
|
||||
self.respond(Response::GetColor { color });
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ use {
|
|||
crate::{
|
||||
async_engine::AsyncEngine,
|
||||
fixed::Fixed,
|
||||
gfx_api::GfxApiOpt,
|
||||
ifs::wl_output::WlOutputGlobal,
|
||||
rect::{Rect, Region},
|
||||
renderer::renderer_base::RendererBase,
|
||||
|
|
@ -93,7 +94,7 @@ impl DamageVisualizer {
|
|||
entry_added: Default::default(),
|
||||
enabled: Default::default(),
|
||||
decay: Cell::new(Duration::from_secs(2)),
|
||||
color: Cell::new(Color::from_rgba_straight(255, 0, 0, 128)),
|
||||
color: Cell::new(Color::from_srgba_straight(255, 0, 0, 128)),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -160,6 +161,7 @@ impl DamageVisualizer {
|
|||
let dx = -cursor_rect.x1();
|
||||
let dy = -cursor_rect.y1();
|
||||
let decay_millis = decay.as_millis() as u64 as f32;
|
||||
renderer.ops.push(GfxApiOpt::Sync);
|
||||
for entry in entries.iter().rev() {
|
||||
let region = Region::new(entry.rect);
|
||||
let region = region.subtract(&used);
|
||||
|
|
|
|||
|
|
@ -352,25 +352,16 @@ impl dyn GfxFramebuffer {
|
|||
acquire_sync: AcquireSync,
|
||||
release_sync: ReleaseSync,
|
||||
) -> Result<Option<SyncFile>, GfxError> {
|
||||
self.clear_with(acquire_sync, release_sync, 0.0, 0.0, 0.0, 0.0)
|
||||
self.clear_with(acquire_sync, release_sync, &Color::TRANSPARENT)
|
||||
}
|
||||
|
||||
pub fn clear_with(
|
||||
self: &Rc<Self>,
|
||||
acquire_sync: AcquireSync,
|
||||
release_sync: ReleaseSync,
|
||||
r: f32,
|
||||
g: f32,
|
||||
b: f32,
|
||||
a: f32,
|
||||
color: &Color,
|
||||
) -> Result<Option<SyncFile>, GfxError> {
|
||||
self.render(
|
||||
acquire_sync,
|
||||
release_sync,
|
||||
&[],
|
||||
Some(&Color { r, g, b, a }),
|
||||
None,
|
||||
)
|
||||
self.render(acquire_sync, release_sync, &[], Some(color), None)
|
||||
}
|
||||
|
||||
pub fn logical_size(&self, transform: Transform) -> (i32, i32) {
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ use {
|
|||
GL_TRIANGLE_STRIP, GL_TRIANGLES,
|
||||
},
|
||||
},
|
||||
theme::Color,
|
||||
theme::{Color, TransferFunction},
|
||||
utils::{errorfmt::ErrorFmt, rc_eq::rc_eq, vecstorage::VecStorage},
|
||||
video::{
|
||||
dmabuf::DMA_BUF_SYNC_READ,
|
||||
|
|
@ -305,10 +305,11 @@ fn run_ops(fb: &Framebuffer, ops: &[GfxApiOpt]) -> Option<SyncFile> {
|
|||
}
|
||||
|
||||
fn fill_boxes3(ctx: &GlRenderContext, boxes: &[[f32; 2]], color: &Color) {
|
||||
let [r, g, b, a] = color.to_array(TransferFunction::Srgb);
|
||||
let gles = ctx.ctx.dpy.gles;
|
||||
unsafe {
|
||||
(gles.glUseProgram)(ctx.fill_prog.prog);
|
||||
(gles.glUniform4f)(ctx.fill_prog_color, color.r, color.g, color.b, color.a);
|
||||
(gles.glUniform4f)(ctx.fill_prog_color, r, g, b, a);
|
||||
(gles.glVertexAttribPointer)(
|
||||
ctx.fill_prog_pos as _,
|
||||
2,
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ use {
|
|||
sys::{GL_ONE, GL_ONE_MINUS_SRC_ALPHA},
|
||||
},
|
||||
rect::Region,
|
||||
theme::Color,
|
||||
theme::{Color, TransferFunction},
|
||||
},
|
||||
std::{
|
||||
cell::Cell,
|
||||
|
|
@ -78,7 +78,8 @@ impl Framebuffer {
|
|||
(gles.glBindFramebuffer)(GL_FRAMEBUFFER, self.gl.fbo);
|
||||
(gles.glViewport)(0, 0, self.gl.width, self.gl.height);
|
||||
if let Some(c) = clear {
|
||||
(gles.glClearColor)(c.r, c.g, c.b, c.a);
|
||||
let [r, g, b, a] = c.to_array(TransferFunction::Srgb);
|
||||
(gles.glClearColor)(r, g, b, a);
|
||||
(gles.glClear)(GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
(gles.glBlendFunc)(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
mod allocator;
|
||||
mod blend_buffer;
|
||||
mod bo_allocator;
|
||||
mod buffer_cache;
|
||||
mod command;
|
||||
mod descriptor;
|
||||
mod descriptor_buffer;
|
||||
|
|
@ -17,6 +18,7 @@ mod shaders;
|
|||
mod shm_image;
|
||||
mod staging;
|
||||
mod transfer;
|
||||
mod transfer_functions;
|
||||
|
||||
use {
|
||||
crate::{
|
||||
|
|
|
|||
163
src/gfx_apis/vulkan/buffer_cache.rs
Normal file
163
src/gfx_apis/vulkan/buffer_cache.rs
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
use {
|
||||
crate::{
|
||||
gfx_apis::vulkan::{
|
||||
VulkanError,
|
||||
allocator::{VulkanAllocation, VulkanAllocator},
|
||||
device::VulkanDevice,
|
||||
},
|
||||
utils::on_drop::OnDrop,
|
||||
},
|
||||
ash::vk::{
|
||||
Buffer, BufferCreateInfo, BufferDeviceAddressInfo, BufferUsageFlags, DeviceAddress,
|
||||
DeviceSize,
|
||||
},
|
||||
gpu_alloc::UsageFlags,
|
||||
std::{cell::RefCell, mem::ManuallyDrop, rc::Rc},
|
||||
};
|
||||
|
||||
pub struct VulkanBufferCache {
|
||||
device: Rc<VulkanDevice>,
|
||||
allocator: Rc<VulkanAllocator>,
|
||||
buffers: RefCell<Vec<VulkanBufferUnused>>,
|
||||
usage: BufferUsageFlags,
|
||||
}
|
||||
|
||||
pub struct VulkanBuffer {
|
||||
cache: Rc<VulkanBufferCache>,
|
||||
pub buffer: ManuallyDrop<VulkanBufferUnused>,
|
||||
}
|
||||
|
||||
pub struct VulkanBufferUnused {
|
||||
device: Rc<VulkanDevice>,
|
||||
pub size: DeviceSize,
|
||||
pub buffer: Buffer,
|
||||
pub allocation: VulkanAllocation,
|
||||
pub address: DeviceAddress,
|
||||
}
|
||||
|
||||
impl VulkanBufferCache {
|
||||
pub fn new(
|
||||
device: &Rc<VulkanDevice>,
|
||||
allocator: &Rc<VulkanAllocator>,
|
||||
usage: BufferUsageFlags,
|
||||
) -> Rc<Self> {
|
||||
Rc::new(Self {
|
||||
device: device.clone(),
|
||||
allocator: allocator.clone(),
|
||||
buffers: Default::default(),
|
||||
usage,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn for_descriptor_buffer(
|
||||
device: &Rc<VulkanDevice>,
|
||||
allocator: &Rc<VulkanAllocator>,
|
||||
has_sampler: bool,
|
||||
) -> Rc<Self> {
|
||||
let mut usage = BufferUsageFlags::RESOURCE_DESCRIPTOR_BUFFER_EXT
|
||||
| BufferUsageFlags::SHADER_DEVICE_ADDRESS;
|
||||
if has_sampler {
|
||||
usage |= BufferUsageFlags::SAMPLER_DESCRIPTOR_BUFFER_EXT;
|
||||
}
|
||||
Self::new(device, allocator, usage)
|
||||
}
|
||||
|
||||
pub fn usage(&self) -> BufferUsageFlags {
|
||||
self.usage
|
||||
}
|
||||
|
||||
pub fn allocate(
|
||||
self: &Rc<Self>,
|
||||
capacity: DeviceSize,
|
||||
align: DeviceSize,
|
||||
) -> Result<VulkanBuffer, VulkanError> {
|
||||
const MIN_ALLOCATION: DeviceSize = 1024;
|
||||
let capacity = capacity.max(MIN_ALLOCATION);
|
||||
let mut smallest = None;
|
||||
let mut smallest_size = DeviceSize::MAX;
|
||||
let mut fitting = None;
|
||||
let mut fitting_size = DeviceSize::MAX;
|
||||
let buffers = &mut *self.buffers.borrow_mut();
|
||||
for (idx, buffer) in buffers.iter().enumerate() {
|
||||
if buffer.size >= capacity {
|
||||
if buffer.size < fitting_size {
|
||||
fitting = Some(idx);
|
||||
fitting_size = buffer.size;
|
||||
}
|
||||
} else {
|
||||
if buffer.size < smallest_size {
|
||||
smallest = Some(idx);
|
||||
smallest_size = buffer.size;
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(idx) = fitting {
|
||||
return Ok(VulkanBuffer {
|
||||
cache: self.clone(),
|
||||
buffer: ManuallyDrop::new(buffers.swap_remove(idx)),
|
||||
});
|
||||
}
|
||||
if let Some(idx) = smallest {
|
||||
log::debug!("discarding size {}", smallest_size);
|
||||
buffers.swap_remove(idx);
|
||||
}
|
||||
let size = capacity.checked_next_power_of_two().unwrap();
|
||||
log::debug!("allocating size {}", size);
|
||||
let buffer = {
|
||||
let info = BufferCreateInfo::default().size(size).usage(self.usage);
|
||||
unsafe {
|
||||
self.device
|
||||
.device
|
||||
.create_buffer(&info, None)
|
||||
.map_err(VulkanError::CreateBuffer)?
|
||||
}
|
||||
};
|
||||
let destroy_buffer = OnDrop(|| unsafe { self.device.device.destroy_buffer(buffer, None) });
|
||||
let mut memory_requirements =
|
||||
unsafe { self.device.device.get_buffer_memory_requirements(buffer) };
|
||||
memory_requirements.alignment = memory_requirements.alignment.max(align);
|
||||
let allocation = {
|
||||
let flags = UsageFlags::UPLOAD
|
||||
| UsageFlags::FAST_DEVICE_ACCESS
|
||||
| UsageFlags::HOST_ACCESS
|
||||
| UsageFlags::DEVICE_ADDRESS;
|
||||
self.allocator.alloc(&memory_requirements, flags, true)?
|
||||
};
|
||||
unsafe {
|
||||
self.device
|
||||
.device
|
||||
.bind_buffer_memory(buffer, allocation.memory, allocation.offset)
|
||||
.map_err(VulkanError::BindBufferMemory)?;
|
||||
}
|
||||
destroy_buffer.forget();
|
||||
let address = {
|
||||
let info = BufferDeviceAddressInfo::default().buffer(buffer);
|
||||
unsafe { self.device.device.get_buffer_device_address(&info) }
|
||||
};
|
||||
Ok(VulkanBuffer {
|
||||
cache: self.clone(),
|
||||
buffer: ManuallyDrop::new(VulkanBufferUnused {
|
||||
device: self.device.clone(),
|
||||
size,
|
||||
buffer,
|
||||
allocation,
|
||||
address,
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for VulkanBuffer {
|
||||
fn drop(&mut self) {
|
||||
let buffer = unsafe { ManuallyDrop::take(&mut self.buffer) };
|
||||
self.cache.buffers.borrow_mut().push(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for VulkanBufferUnused {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
self.device.device.destroy_buffer(self.buffer, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,41 +1,8 @@
|
|||
use {
|
||||
crate::{
|
||||
gfx_apis::vulkan::{
|
||||
VulkanError,
|
||||
allocator::{VulkanAllocation, VulkanAllocator},
|
||||
descriptor::VulkanDescriptorSetLayout,
|
||||
device::VulkanDevice,
|
||||
},
|
||||
utils::on_drop::OnDrop,
|
||||
},
|
||||
ash::vk::{
|
||||
Buffer, BufferCreateInfo, BufferDeviceAddressInfo, BufferUsageFlags, DeviceAddress,
|
||||
DeviceSize,
|
||||
},
|
||||
gpu_alloc::UsageFlags,
|
||||
std::{cell::RefCell, mem::ManuallyDrop, ops::Deref, rc::Rc},
|
||||
crate::gfx_apis::vulkan::descriptor::VulkanDescriptorSetLayout, ash::vk::DeviceSize,
|
||||
std::ops::Deref,
|
||||
};
|
||||
|
||||
pub struct VulkanDescriptorBufferCache {
|
||||
device: Rc<VulkanDevice>,
|
||||
allocator: Rc<VulkanAllocator>,
|
||||
buffers: RefCell<Vec<VulkanDescriptorBufferUnused>>,
|
||||
has_sampler: bool,
|
||||
}
|
||||
|
||||
pub struct VulkanDescriptorBuffer {
|
||||
cache: Rc<VulkanDescriptorBufferCache>,
|
||||
pub buffer: ManuallyDrop<VulkanDescriptorBufferUnused>,
|
||||
}
|
||||
|
||||
pub struct VulkanDescriptorBufferUnused {
|
||||
device: Rc<VulkanDevice>,
|
||||
pub size: DeviceSize,
|
||||
pub buffer: Buffer,
|
||||
pub allocation: VulkanAllocation,
|
||||
pub address: DeviceAddress,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct VulkanDescriptorBufferWriter {
|
||||
buffer: Vec<u8>,
|
||||
|
|
@ -45,124 +12,6 @@ pub struct VulkanDescriptorBufferSetWriter<'a> {
|
|||
set: &'a mut [u8],
|
||||
}
|
||||
|
||||
impl VulkanDescriptorBufferCache {
|
||||
pub fn new(
|
||||
device: &Rc<VulkanDevice>,
|
||||
allocator: &Rc<VulkanAllocator>,
|
||||
has_sampler: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
device: device.clone(),
|
||||
allocator: allocator.clone(),
|
||||
buffers: Default::default(),
|
||||
has_sampler,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn allocate(
|
||||
self: &Rc<Self>,
|
||||
capacity: DeviceSize,
|
||||
) -> Result<VulkanDescriptorBuffer, VulkanError> {
|
||||
const MIN_ALLOCATION: DeviceSize = 1024;
|
||||
let capacity = capacity.max(MIN_ALLOCATION);
|
||||
let mut smallest = None;
|
||||
let mut smallest_size = DeviceSize::MAX;
|
||||
let mut fitting = None;
|
||||
let mut fitting_size = DeviceSize::MAX;
|
||||
let buffers = &mut *self.buffers.borrow_mut();
|
||||
for (idx, buffer) in buffers.iter().enumerate() {
|
||||
if buffer.size >= capacity {
|
||||
if buffer.size < fitting_size {
|
||||
fitting = Some(idx);
|
||||
fitting_size = buffer.size;
|
||||
}
|
||||
} else {
|
||||
if buffer.size < smallest_size {
|
||||
smallest = Some(idx);
|
||||
smallest_size = buffer.size;
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(idx) = fitting {
|
||||
return Ok(VulkanDescriptorBuffer {
|
||||
cache: self.clone(),
|
||||
buffer: ManuallyDrop::new(buffers.swap_remove(idx)),
|
||||
});
|
||||
}
|
||||
if let Some(idx) = smallest {
|
||||
log::debug!("discarding size {}", smallest_size);
|
||||
buffers.swap_remove(idx);
|
||||
}
|
||||
let size = capacity.checked_next_power_of_two().unwrap();
|
||||
log::debug!("allocating size {}", size);
|
||||
let buffer = {
|
||||
let usage = self.usage();
|
||||
let info = BufferCreateInfo::default().size(size).usage(usage);
|
||||
unsafe {
|
||||
self.device
|
||||
.device
|
||||
.create_buffer(&info, None)
|
||||
.map_err(VulkanError::CreateBuffer)?
|
||||
}
|
||||
};
|
||||
let destroy_buffer = OnDrop(|| unsafe { self.device.device.destroy_buffer(buffer, None) });
|
||||
let memory_requirements =
|
||||
unsafe { self.device.device.get_buffer_memory_requirements(buffer) };
|
||||
let allocation = {
|
||||
let flags = UsageFlags::UPLOAD
|
||||
| UsageFlags::FAST_DEVICE_ACCESS
|
||||
| UsageFlags::HOST_ACCESS
|
||||
| UsageFlags::DEVICE_ADDRESS;
|
||||
self.allocator.alloc(&memory_requirements, flags, true)?
|
||||
};
|
||||
unsafe {
|
||||
self.device
|
||||
.device
|
||||
.bind_buffer_memory(buffer, allocation.memory, allocation.offset)
|
||||
.map_err(VulkanError::BindBufferMemory)?;
|
||||
}
|
||||
destroy_buffer.forget();
|
||||
let address = {
|
||||
let info = BufferDeviceAddressInfo::default().buffer(buffer);
|
||||
unsafe { self.device.device.get_buffer_device_address(&info) }
|
||||
};
|
||||
Ok(VulkanDescriptorBuffer {
|
||||
cache: self.clone(),
|
||||
buffer: ManuallyDrop::new(VulkanDescriptorBufferUnused {
|
||||
device: self.device.clone(),
|
||||
size,
|
||||
buffer,
|
||||
allocation,
|
||||
address,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn usage(&self) -> BufferUsageFlags {
|
||||
let mut usage = BufferUsageFlags::RESOURCE_DESCRIPTOR_BUFFER_EXT
|
||||
| BufferUsageFlags::SHADER_DEVICE_ADDRESS;
|
||||
if self.has_sampler {
|
||||
usage |= BufferUsageFlags::SAMPLER_DESCRIPTOR_BUFFER_EXT;
|
||||
}
|
||||
usage
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for VulkanDescriptorBuffer {
|
||||
fn drop(&mut self) {
|
||||
let buffer = unsafe { ManuallyDrop::take(&mut self.buffer) };
|
||||
self.cache.buffers.borrow_mut().push(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for VulkanDescriptorBufferUnused {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
self.device.device.destroy_buffer(self.buffer, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VulkanDescriptorBufferWriter {
|
||||
pub fn clear(&mut self) {
|
||||
self.buffer.clear();
|
||||
|
|
|
|||
|
|
@ -39,7 +39,8 @@ pub(super) struct PipelineCreateInfo {
|
|||
pub(super) blend: bool,
|
||||
pub(super) src_has_alpha: bool,
|
||||
pub(super) has_alpha_mult: bool,
|
||||
pub(super) with_linear_output: bool,
|
||||
pub(super) eotf: u32,
|
||||
pub(super) oetf: u32,
|
||||
pub(super) frag_descriptor_set_layout: Option<Rc<VulkanDescriptorSetLayout>>,
|
||||
}
|
||||
|
||||
|
|
@ -48,13 +49,13 @@ impl VulkanDevice {
|
|||
&self,
|
||||
info: PipelineCreateInfo,
|
||||
) -> Result<Rc<VulkanPipeline>, VulkanError> {
|
||||
self.create_pipeline_(info, size_of::<P>() as _)
|
||||
self.create_pipeline2(info, size_of::<P>())
|
||||
}
|
||||
|
||||
fn create_pipeline_(
|
||||
pub(super) fn create_pipeline2(
|
||||
&self,
|
||||
info: PipelineCreateInfo,
|
||||
push_size: u32,
|
||||
push_size: usize,
|
||||
) -> Result<Rc<VulkanPipeline>, VulkanError> {
|
||||
let pipeline_layout = {
|
||||
let mut push_constant_ranges = ArrayVec::<_, 1>::new();
|
||||
|
|
@ -63,7 +64,7 @@ impl VulkanDevice {
|
|||
PushConstantRange::default()
|
||||
.stage_flags(ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT)
|
||||
.offset(0)
|
||||
.size(push_size),
|
||||
.size(push_size as u32),
|
||||
);
|
||||
}
|
||||
let mut descriptor_set_layouts = ArrayVec::<_, 1>::new();
|
||||
|
|
@ -77,8 +78,8 @@ impl VulkanDevice {
|
|||
};
|
||||
let destroy_layout =
|
||||
OnDrop(|| unsafe { self.device.destroy_pipeline_layout(pipeline_layout, None) });
|
||||
let mut frag_spec_data = ArrayVec::<_, { 3 * 4 }>::new();
|
||||
let mut frag_spec_entries = ArrayVec::<_, 3>::new();
|
||||
let mut frag_spec_data = ArrayVec::<_, { 4 * 4 }>::new();
|
||||
let mut frag_spec_entries = ArrayVec::<_, 4>::new();
|
||||
let mut frag_spec_entry = |data: &[u8]| {
|
||||
let entry = SpecializationMapEntry::default()
|
||||
.constant_id(frag_spec_entries.len() as _)
|
||||
|
|
@ -89,7 +90,8 @@ impl VulkanDevice {
|
|||
};
|
||||
frag_spec_entry(&(info.src_has_alpha as u32).to_ne_bytes());
|
||||
frag_spec_entry(&(info.has_alpha_mult as u32).to_ne_bytes());
|
||||
frag_spec_entry(&(info.with_linear_output as u32).to_ne_bytes());
|
||||
frag_spec_entry(&info.eotf.to_ne_bytes());
|
||||
frag_spec_entry(&info.oetf.to_ne_bytes());
|
||||
let frag_spec = SpecializationInfo::default()
|
||||
.map_entries(&frag_spec_entries)
|
||||
.data(&frag_spec_data);
|
||||
|
|
|
|||
|
|
@ -10,11 +10,10 @@ use {
|
|||
gfx_apis::vulkan::{
|
||||
VulkanError,
|
||||
allocator::{VulkanAllocator, VulkanThreadedAllocator},
|
||||
buffer_cache::{VulkanBuffer, VulkanBufferCache},
|
||||
command::{VulkanCommandBuffer, VulkanCommandPool},
|
||||
descriptor::VulkanDescriptorSetLayout,
|
||||
descriptor_buffer::{
|
||||
VulkanDescriptorBuffer, VulkanDescriptorBufferCache, VulkanDescriptorBufferWriter,
|
||||
},
|
||||
descriptor_buffer::VulkanDescriptorBufferWriter,
|
||||
device::VulkanDevice,
|
||||
fence::VulkanFence,
|
||||
image::{QueueFamily, QueueState, QueueTransfer, VulkanImage, VulkanImageMemory},
|
||||
|
|
@ -22,17 +21,17 @@ use {
|
|||
sampler::VulkanSampler,
|
||||
semaphore::VulkanSemaphore,
|
||||
shaders::{
|
||||
FILL_FRAG, FILL_VERT, FillPushConstants, OUT_FRAG, OUT_VERT, OutPushConstants,
|
||||
TEX_FRAG, TEX_VERT, TexPushConstants, VulkanShader,
|
||||
FILL_FRAG, FILL_VERT, FillPushConstants, LEGACY_FILL_FRAG, LEGACY_FILL_VERT,
|
||||
LEGACY_TEX_FRAG, LEGACY_TEX_VERT, LegacyFillPushConstants, LegacyTexPushConstants,
|
||||
OUT_FRAG, OUT_VERT, OutPushConstants, TEX_FRAG, TEX_VERT, TexPushConstants,
|
||||
TexVertex, VulkanShader,
|
||||
},
|
||||
transfer_functions::{TF_LINEAR, TF_SRGB},
|
||||
},
|
||||
io_uring::IoUring,
|
||||
rect::{Rect, Region},
|
||||
theme::Color,
|
||||
utils::{
|
||||
copyhashmap::CopyHashMap, errorfmt::ErrorFmt, numcell::NumCell, once::Once,
|
||||
stack::Stack,
|
||||
},
|
||||
theme::{Color, TransferFunction},
|
||||
utils::{copyhashmap::CopyHashMap, errorfmt::ErrorFmt, numcell::NumCell, stack::Stack},
|
||||
video::dmabuf::{DMA_BUF_SYNC_READ, DMA_BUF_SYNC_WRITE, dma_buf_export_sync_file},
|
||||
},
|
||||
ahash::AHashMap,
|
||||
|
|
@ -40,26 +39,29 @@ use {
|
|||
ash::{
|
||||
Device,
|
||||
vk::{
|
||||
self, AccessFlags2, AttachmentLoadOp, AttachmentStoreOp, ClearAttachment,
|
||||
ClearColorValue, ClearRect, ClearValue, CommandBuffer, CommandBufferBeginInfo,
|
||||
CommandBufferSubmitInfo, CommandBufferUsageFlags, CopyImageInfo2, DependencyInfoKHR,
|
||||
DescriptorBufferBindingInfoEXT, DescriptorImageInfo, DescriptorType, DeviceSize,
|
||||
Extent2D, Extent3D, ImageAspectFlags, ImageCopy2, ImageLayout, ImageMemoryBarrier2,
|
||||
ImageSubresourceLayers, ImageSubresourceRange, Offset2D, Offset3D, PipelineBindPoint,
|
||||
PipelineStageFlags2, QUEUE_FAMILY_FOREIGN_EXT, Rect2D, RenderingAttachmentInfo,
|
||||
RenderingInfo, SemaphoreSubmitInfo, SemaphoreSubmitInfoKHR, ShaderStageFlags,
|
||||
SubmitInfo2, Viewport, WriteDescriptorSet,
|
||||
self, AccessFlags2, AttachmentLoadOp, AttachmentStoreOp, BufferUsageFlags,
|
||||
ClearAttachment, ClearColorValue, ClearRect, ClearValue, CommandBuffer,
|
||||
CommandBufferBeginInfo, CommandBufferSubmitInfo, CommandBufferUsageFlags,
|
||||
CopyImageInfo2, DependencyInfoKHR, DescriptorBufferBindingInfoEXT, DescriptorImageInfo,
|
||||
DescriptorType, DeviceAddress, DeviceSize, Extent2D, Extent3D, ImageAspectFlags,
|
||||
ImageCopy2, ImageLayout, ImageMemoryBarrier2, ImageSubresourceLayers,
|
||||
ImageSubresourceRange, Offset2D, Offset3D, PipelineBindPoint, PipelineStageFlags2,
|
||||
QUEUE_FAMILY_FOREIGN_EXT, Rect2D, RenderingAttachmentInfo, RenderingInfo,
|
||||
SemaphoreSubmitInfo, SemaphoreSubmitInfoKHR, ShaderStageFlags, SubmitInfo2, Viewport,
|
||||
WriteDescriptorSet,
|
||||
},
|
||||
},
|
||||
isnt::std_1::{collections::IsntHashMapExt, primitive::IsntSliceExt},
|
||||
jay_algorithms::rect::Tag,
|
||||
linearize::{Linearize, StaticMap, static_map},
|
||||
linearize::{Linearize, LinearizeExt, StaticMap, static_map},
|
||||
std::{
|
||||
borrow::Cow,
|
||||
cell::{Cell, RefCell},
|
||||
collections::hash_map::Entry,
|
||||
fmt::{Debug, Formatter},
|
||||
mem, ptr,
|
||||
mem,
|
||||
ops::Range,
|
||||
ptr,
|
||||
rc::{Rc, Weak},
|
||||
slice,
|
||||
},
|
||||
|
|
@ -94,9 +96,10 @@ pub struct VulkanRenderer {
|
|||
pub(super) pending_cpu_jobs: CopyHashMap<u64, PendingJob>,
|
||||
pub(super) shm_allocator: Rc<VulkanThreadedAllocator>,
|
||||
pub(super) sampler: Rc<VulkanSampler>,
|
||||
pub(super) sampler_descriptor_buffer_cache: Rc<VulkanDescriptorBufferCache>,
|
||||
pub(super) resource_descriptor_buffer_cache: Rc<VulkanDescriptorBufferCache>,
|
||||
pub(super) sampler_descriptor_buffer_cache: Rc<VulkanBufferCache>,
|
||||
pub(super) resource_descriptor_buffer_cache: Rc<VulkanBufferCache>,
|
||||
pub(super) blend_buffers: RefCell<AHashMap<(u32, u32), Weak<VulkanImage>>>,
|
||||
pub(super) shader_buffer_cache: Rc<VulkanBufferCache>,
|
||||
}
|
||||
|
||||
pub(super) struct CachedCommandBuffers {
|
||||
|
|
@ -126,13 +129,13 @@ pub(super) struct UsedTexture {
|
|||
release_sync: ReleaseSync,
|
||||
}
|
||||
|
||||
#[derive(Linearize)]
|
||||
#[derive(Copy, Clone, Linearize)]
|
||||
pub(super) enum TexCopyType {
|
||||
Identity,
|
||||
Multiply,
|
||||
}
|
||||
|
||||
#[derive(Linearize)]
|
||||
#[derive(Copy, Clone, Linearize)]
|
||||
pub(super) enum TexSourceType {
|
||||
Opaque,
|
||||
HasAlpha,
|
||||
|
|
@ -148,7 +151,8 @@ pub(super) struct Memory {
|
|||
wait_semaphore_infos: Vec<SemaphoreSubmitInfo<'static>>,
|
||||
release_fence: Option<Rc<VulkanFence>>,
|
||||
release_sync_file: Option<SyncFile>,
|
||||
descriptor_buffers: ArrayVec<VulkanDescriptorBuffer, 2>,
|
||||
used_buffers: ArrayVec<VulkanBuffer, 3>,
|
||||
paint_bounds: StaticMap<RenderPass, Option<PaintRegion>>,
|
||||
paint_regions: StaticMap<RenderPass, Vec<PaintRegion>>,
|
||||
clear_rects: StaticMap<RenderPass, Vec<ClearRect>>,
|
||||
image_copy_regions: Vec<ImageCopy2<'static>>,
|
||||
|
|
@ -156,6 +160,40 @@ pub(super) struct Memory {
|
|||
resource_descriptor_buffer_writer: VulkanDescriptorBufferWriter,
|
||||
regions_1: Vec<Rect>,
|
||||
regions_2: Vec<Rect<u32>>,
|
||||
ops: StaticMap<RenderPass, Vec<VulkanOp>>,
|
||||
ops_tmp: StaticMap<RenderPass, Vec<VulkanOp>>,
|
||||
fill_targets: Vec<Point>,
|
||||
tex_targets: Vec<[Point; 2]>,
|
||||
data_buffer: Vec<u8>,
|
||||
out_address: DeviceAddress,
|
||||
}
|
||||
|
||||
type Point = [[f32; 2]; 4];
|
||||
|
||||
enum VulkanOp {
|
||||
Fill(VulkanFillOp),
|
||||
Tex(VulkanTexOp),
|
||||
}
|
||||
|
||||
struct VulkanTexOp {
|
||||
tex: Rc<VulkanImage>,
|
||||
range: Range<usize>,
|
||||
buffer_resv: Option<Rc<dyn BufferResv>>,
|
||||
acquire_sync: AcquireSync,
|
||||
release_sync: ReleaseSync,
|
||||
alpha: f32,
|
||||
source_type: TexSourceType,
|
||||
copy_type: TexCopyType,
|
||||
range_address: DeviceAddress,
|
||||
instances: u32,
|
||||
}
|
||||
|
||||
struct VulkanFillOp {
|
||||
range: Range<usize>,
|
||||
color: [f32; 4],
|
||||
source_type: TexSourceType,
|
||||
range_address: DeviceAddress,
|
||||
instances: u32,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Linearize, Eq, PartialEq)]
|
||||
|
|
@ -164,6 +202,7 @@ pub(super) enum RenderPass {
|
|||
FrameBuffer,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
struct PaintRegion {
|
||||
x1: f32,
|
||||
y1: f32,
|
||||
|
|
@ -181,7 +220,7 @@ pub(super) struct PendingFrame {
|
|||
wait_semaphores: Cell<Vec<Rc<VulkanSemaphore>>>,
|
||||
waiter: Cell<Option<SpawnedFuture<()>>>,
|
||||
_release_fence: Option<Rc<VulkanFence>>,
|
||||
_descriptor_buffers: ArrayVec<VulkanDescriptorBuffer, 2>,
|
||||
_used_buffers: ArrayVec<VulkanBuffer, 3>,
|
||||
}
|
||||
|
||||
pub(super) struct VulkanFormatPipelines {
|
||||
|
|
@ -195,8 +234,21 @@ impl VulkanDevice {
|
|||
eng: &Rc<AsyncEngine>,
|
||||
ring: &Rc<IoUring>,
|
||||
) -> Result<Rc<VulkanRenderer>, VulkanError> {
|
||||
let fill_vert_shader = self.create_shader(FILL_VERT)?;
|
||||
let fill_frag_shader = self.create_shader(FILL_FRAG)?;
|
||||
let fill_vert_shader;
|
||||
let fill_frag_shader;
|
||||
let tex_vert_shader;
|
||||
let tex_frag_shader;
|
||||
if self.descriptor_buffer.is_some() {
|
||||
tex_vert_shader = self.create_shader(TEX_VERT)?;
|
||||
tex_frag_shader = self.create_shader(TEX_FRAG)?;
|
||||
fill_vert_shader = self.create_shader(FILL_VERT)?;
|
||||
fill_frag_shader = self.create_shader(FILL_FRAG)?;
|
||||
} else {
|
||||
tex_vert_shader = self.create_shader(LEGACY_TEX_VERT)?;
|
||||
tex_frag_shader = self.create_shader(LEGACY_TEX_FRAG)?;
|
||||
fill_vert_shader = self.create_shader(LEGACY_FILL_VERT)?;
|
||||
fill_frag_shader = self.create_shader(LEGACY_FILL_FRAG)?;
|
||||
}
|
||||
let sampler = self.create_sampler()?;
|
||||
let tex_descriptor_set_layout = self.create_tex_descriptor_set_layout(&sampler)?;
|
||||
let out_descriptor_set_layout = self
|
||||
|
|
@ -206,8 +258,6 @@ impl VulkanDevice {
|
|||
.transpose()?;
|
||||
let out_vert_shader = self.create_shader(OUT_VERT)?;
|
||||
let out_frag_shader = self.create_shader(OUT_FRAG)?;
|
||||
let tex_vert_shader = self.create_shader(TEX_VERT)?;
|
||||
let tex_frag_shader = self.create_shader(TEX_FRAG)?;
|
||||
let gfx_command_buffers = self.create_command_pool(self.graphics_queue_idx)?;
|
||||
let transfer_command_buffers = self
|
||||
.distinct_transfer_queue_family_idx
|
||||
|
|
@ -247,9 +297,14 @@ impl VulkanDevice {
|
|||
let allocator = self.create_allocator()?;
|
||||
let shm_allocator = self.create_threaded_allocator()?;
|
||||
let sampler_descriptor_buffer_cache =
|
||||
Rc::new(VulkanDescriptorBufferCache::new(self, &allocator, true));
|
||||
VulkanBufferCache::for_descriptor_buffer(self, &allocator, true);
|
||||
let resource_descriptor_buffer_cache =
|
||||
Rc::new(VulkanDescriptorBufferCache::new(self, &allocator, false));
|
||||
VulkanBufferCache::for_descriptor_buffer(self, &allocator, false);
|
||||
let shader_buffer_cache = {
|
||||
// TODO: https://github.com/KhronosGroup/Vulkan-Samples/issues/1286
|
||||
let usage = BufferUsageFlags::SHADER_DEVICE_ADDRESS | BufferUsageFlags::STORAGE_BUFFER;
|
||||
VulkanBufferCache::new(self, &allocator, usage)
|
||||
};
|
||||
let render = Rc::new(VulkanRenderer {
|
||||
formats: Rc::new(formats),
|
||||
device: self.clone(),
|
||||
|
|
@ -281,6 +336,7 @@ impl VulkanDevice {
|
|||
sampler_descriptor_buffer_cache,
|
||||
resource_descriptor_buffer_cache,
|
||||
blend_buffers: Default::default(),
|
||||
shader_buffer_cache,
|
||||
});
|
||||
render.get_or_create_pipelines(XRGB8888.vk_format, RenderPass::FrameBuffer)?;
|
||||
Ok(render)
|
||||
|
|
@ -293,38 +349,53 @@ impl VulkanRenderer {
|
|||
format: vk::Format,
|
||||
pass: RenderPass,
|
||||
) -> Result<Rc<VulkanFormatPipelines>, VulkanError> {
|
||||
let with_linear_output = pass == RenderPass::BlendBuffer;
|
||||
let (eotf, oetf) = match pass {
|
||||
RenderPass::BlendBuffer => (TF_SRGB, TF_LINEAR),
|
||||
RenderPass::FrameBuffer => (TF_SRGB, TF_SRGB),
|
||||
};
|
||||
let pipelines = &self.pipelines[pass];
|
||||
if let Some(pl) = pipelines.get(&format) {
|
||||
return Ok(pl);
|
||||
}
|
||||
let create_fill_pipeline = |src_has_alpha| {
|
||||
self.device
|
||||
.create_pipeline::<FillPushConstants>(PipelineCreateInfo {
|
||||
format,
|
||||
vert: self.fill_vert_shader.clone(),
|
||||
frag: self.fill_frag_shader.clone(),
|
||||
blend: src_has_alpha,
|
||||
src_has_alpha,
|
||||
has_alpha_mult: false,
|
||||
with_linear_output,
|
||||
frag_descriptor_set_layout: None,
|
||||
})
|
||||
let push_size = if self.device.descriptor_buffer.is_some() {
|
||||
size_of::<FillPushConstants>()
|
||||
} else {
|
||||
size_of::<LegacyFillPushConstants>()
|
||||
};
|
||||
let info = PipelineCreateInfo {
|
||||
format,
|
||||
vert: self.fill_vert_shader.clone(),
|
||||
frag: self.fill_frag_shader.clone(),
|
||||
blend: src_has_alpha,
|
||||
src_has_alpha,
|
||||
has_alpha_mult: false,
|
||||
eotf,
|
||||
oetf,
|
||||
frag_descriptor_set_layout: None,
|
||||
};
|
||||
self.device.create_pipeline2(info, push_size)
|
||||
};
|
||||
let fill_opaque = create_fill_pipeline(false)?;
|
||||
let fill_alpha = create_fill_pipeline(true)?;
|
||||
let create_tex_pipeline = |src_has_alpha, has_alpha_mult| {
|
||||
self.device
|
||||
.create_pipeline::<TexPushConstants>(PipelineCreateInfo {
|
||||
format,
|
||||
vert: self.tex_vert_shader.clone(),
|
||||
frag: self.tex_frag_shader.clone(),
|
||||
blend: src_has_alpha || has_alpha_mult,
|
||||
src_has_alpha,
|
||||
has_alpha_mult,
|
||||
with_linear_output,
|
||||
frag_descriptor_set_layout: Some(self.tex_descriptor_set_layout.clone()),
|
||||
})
|
||||
let push_size = if self.device.descriptor_buffer.is_some() {
|
||||
size_of::<TexPushConstants>()
|
||||
} else {
|
||||
size_of::<LegacyTexPushConstants>()
|
||||
};
|
||||
let info = PipelineCreateInfo {
|
||||
format,
|
||||
vert: self.tex_vert_shader.clone(),
|
||||
frag: self.tex_frag_shader.clone(),
|
||||
blend: src_has_alpha || has_alpha_mult,
|
||||
src_has_alpha,
|
||||
has_alpha_mult,
|
||||
eotf,
|
||||
oetf,
|
||||
frag_descriptor_set_layout: Some(self.tex_descriptor_set_layout.clone()),
|
||||
};
|
||||
self.device.create_pipeline2(info, push_size)
|
||||
};
|
||||
let tex_opaque = create_tex_pipeline(false, false)?;
|
||||
let tex_alpha = create_tex_pipeline(true, false)?;
|
||||
|
|
@ -357,7 +428,6 @@ impl VulkanRenderer {
|
|||
fn create_descriptor_buffers(
|
||||
&self,
|
||||
buf: CommandBuffer,
|
||||
opts: &[GfxApiOpt],
|
||||
bb: Option<&VulkanImage>,
|
||||
) -> Result<(), VulkanError> {
|
||||
let Some(db) = &self.device.descriptor_buffer else {
|
||||
|
|
@ -366,7 +436,6 @@ impl VulkanRenderer {
|
|||
zone!("create_descriptor_buffers");
|
||||
let version = self.allocate_point();
|
||||
let memory = &mut *self.memory.borrow_mut();
|
||||
memory.descriptor_buffers.clear();
|
||||
let sampler_writer = &mut memory.sampler_descriptor_buffer_writer;
|
||||
sampler_writer.clear();
|
||||
let resource_writer = &mut memory.resource_descriptor_buffer_writer;
|
||||
|
|
@ -378,28 +447,30 @@ impl VulkanRenderer {
|
|||
let mut writer = resource_writer.add_set(layout);
|
||||
writer.write(layout.offsets[0], &bb.sampled_image_descriptor);
|
||||
}
|
||||
for cmd in opts {
|
||||
let GfxApiOpt::CopyTexture(c) = cmd else {
|
||||
continue;
|
||||
};
|
||||
let tex = c.tex.clone().into_vk(&self.device.device);
|
||||
if tex.descriptor_buffer_version.replace(version) == version {
|
||||
continue;
|
||||
for pass in RenderPass::variants() {
|
||||
for cmd in &memory.ops[pass] {
|
||||
let VulkanOp::Tex(c) = cmd else {
|
||||
continue;
|
||||
};
|
||||
let tex = &c.tex;
|
||||
if tex.descriptor_buffer_version.replace(version) == version {
|
||||
continue;
|
||||
}
|
||||
let offset = sampler_writer.next_offset();
|
||||
tex.descriptor_buffer_offset.set(offset);
|
||||
let mut writer = sampler_writer.add_set(&self.tex_descriptor_set_layout);
|
||||
writer.write(
|
||||
self.tex_descriptor_set_layout.offsets[0],
|
||||
&tex.shader_read_only_optimal_descriptor,
|
||||
);
|
||||
}
|
||||
let offset = sampler_writer.next_offset();
|
||||
tex.descriptor_buffer_offset.set(offset);
|
||||
let mut writer = sampler_writer.add_set(&self.tex_descriptor_set_layout);
|
||||
writer.write(
|
||||
self.tex_descriptor_set_layout.offsets[0],
|
||||
&tex.shader_read_only_optimal_descriptor,
|
||||
);
|
||||
}
|
||||
let mut infos = ArrayVec::<_, 2>::new();
|
||||
for (writer, cache) in [
|
||||
(&sampler_writer, &self.sampler_descriptor_buffer_cache),
|
||||
(&resource_writer, &self.resource_descriptor_buffer_cache),
|
||||
] {
|
||||
let buffer = cache.allocate(writer.len() as DeviceSize)?;
|
||||
let buffer = cache.allocate(writer.len() as DeviceSize, 1)?;
|
||||
buffer.buffer.allocation.upload(|ptr, _| unsafe {
|
||||
ptr::copy_nonoverlapping(writer.as_ptr(), ptr, writer.len())
|
||||
})?;
|
||||
|
|
@ -407,7 +478,7 @@ impl VulkanRenderer {
|
|||
.usage(cache.usage())
|
||||
.address(buffer.buffer.address);
|
||||
infos.push(info);
|
||||
memory.descriptor_buffers.push(buffer);
|
||||
memory.used_buffers.push(buffer);
|
||||
}
|
||||
unsafe {
|
||||
db.cmd_bind_descriptor_buffers(buf, &infos);
|
||||
|
|
@ -415,35 +486,246 @@ impl VulkanRenderer {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn collect_memory(&self, opts: &[GfxApiOpt]) {
|
||||
fn convert_ops(&self, opts: &[GfxApiOpt]) {
|
||||
zone!("convert_ops");
|
||||
let memory = &mut *self.memory.borrow_mut();
|
||||
for ops in memory.ops.values_mut() {
|
||||
ops.clear();
|
||||
}
|
||||
for ops in memory.ops_tmp.values_mut() {
|
||||
ops.clear();
|
||||
}
|
||||
memory.tex_targets.clear();
|
||||
memory.fill_targets.clear();
|
||||
memory.data_buffer.clear();
|
||||
let sync = |memory: &mut Memory| {
|
||||
for pass in RenderPass::variants() {
|
||||
let ops = &mut memory.ops_tmp[pass];
|
||||
ops.sort_unstable_by_key(|o| {
|
||||
#[derive(Eq, PartialEq, PartialOrd, Ord)]
|
||||
enum Key {
|
||||
Fill { color: [u32; 4] },
|
||||
Tex,
|
||||
}
|
||||
match o {
|
||||
VulkanOp::Fill(f) => Key::Fill {
|
||||
color: f.color.map(|c| c.to_bits()),
|
||||
},
|
||||
VulkanOp::Tex(_) => Key::Tex,
|
||||
}
|
||||
});
|
||||
let mops = &mut memory.ops[pass];
|
||||
if self.device.descriptor_buffer.is_none() {
|
||||
mops.append(ops);
|
||||
continue;
|
||||
}
|
||||
for (idx, op) in ops.drain(..).enumerate() {
|
||||
match op {
|
||||
VulkanOp::Fill(mut f) => {
|
||||
f.range_address = memory.data_buffer.len() as DeviceAddress;
|
||||
f.instances = f.range.len() as u32;
|
||||
for pos in &memory.fill_targets[f.range.clone()] {
|
||||
memory.data_buffer.extend_from_slice(uapi::as_bytes(pos));
|
||||
}
|
||||
if let Some(VulkanOp::Fill(p)) = mops.last_mut() {
|
||||
if p.color == f.color && idx > 0 {
|
||||
p.instances += f.instances;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
mops.push(VulkanOp::Fill(f));
|
||||
}
|
||||
VulkanOp::Tex(mut c) => {
|
||||
c.range_address = memory.data_buffer.len() as DeviceAddress;
|
||||
c.instances = c.range.len() as u32;
|
||||
for &[pos, tex_pos] in &memory.tex_targets[c.range.clone()] {
|
||||
let vertex = TexVertex { pos, tex_pos };
|
||||
memory
|
||||
.data_buffer
|
||||
.extend_from_slice(uapi::as_bytes(&vertex));
|
||||
}
|
||||
mops.push(VulkanOp::Tex(c));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
for op in opts {
|
||||
match op {
|
||||
GfxApiOpt::Sync => {
|
||||
sync(memory);
|
||||
}
|
||||
GfxApiOpt::FillRect(fr) => {
|
||||
let target = fr.rect.to_points();
|
||||
for pass in RenderPass::variants() {
|
||||
let Some(bounds) = memory.paint_bounds[pass] else {
|
||||
continue;
|
||||
};
|
||||
if !bounds.intersects(&target) {
|
||||
continue;
|
||||
}
|
||||
let tf = match pass {
|
||||
RenderPass::BlendBuffer => TransferFunction::Linear,
|
||||
RenderPass::FrameBuffer => TransferFunction::Srgb,
|
||||
};
|
||||
let ops = &mut memory.ops_tmp[pass];
|
||||
let lo = memory.fill_targets.len();
|
||||
for region in &memory.paint_regions[pass] {
|
||||
let mut target = target;
|
||||
if !region.constrain(&mut target, None) {
|
||||
continue;
|
||||
}
|
||||
memory.fill_targets.push(target);
|
||||
}
|
||||
let hi = memory.fill_targets.len();
|
||||
if lo == hi {
|
||||
continue;
|
||||
}
|
||||
let color = fr.color.to_array2(tf, fr.alpha);
|
||||
let source_type = match color[3] < 1.0 {
|
||||
false => TexSourceType::Opaque,
|
||||
true => TexSourceType::HasAlpha,
|
||||
};
|
||||
ops.push(VulkanOp::Fill(VulkanFillOp {
|
||||
range: lo..hi,
|
||||
color,
|
||||
source_type,
|
||||
range_address: 0,
|
||||
instances: 0,
|
||||
}));
|
||||
}
|
||||
}
|
||||
GfxApiOpt::CopyTexture(ct) => {
|
||||
let tex = ct.tex.clone().into_vk(&self.device.device);
|
||||
if tex.contents_are_undefined.get() {
|
||||
log::warn!("Ignoring undefined texture");
|
||||
continue;
|
||||
}
|
||||
if tex.queue_state.get().acquire(QueueFamily::Gfx) == QueueTransfer::Impossible
|
||||
{
|
||||
log::warn!("Ignoring texture owned by different queue");
|
||||
continue;
|
||||
}
|
||||
let target = ct.target.to_points();
|
||||
let source = ct.source.to_points();
|
||||
for pass in RenderPass::variants() {
|
||||
let Some(bounds) = memory.paint_bounds[pass] else {
|
||||
continue;
|
||||
};
|
||||
if !bounds.intersects(&target) {
|
||||
continue;
|
||||
}
|
||||
let ops = &mut memory.ops_tmp[pass];
|
||||
let lo = memory.tex_targets.len();
|
||||
for region in &memory.paint_regions[pass] {
|
||||
let mut target = target;
|
||||
let mut source = source;
|
||||
if !region.constrain(&mut target, Some(&mut source)) {
|
||||
continue;
|
||||
}
|
||||
memory.tex_targets.push([target, source]);
|
||||
}
|
||||
let hi = memory.tex_targets.len();
|
||||
if lo == hi {
|
||||
continue;
|
||||
}
|
||||
let copy_type = match ct.alpha.is_some() {
|
||||
true => TexCopyType::Multiply,
|
||||
false => TexCopyType::Identity,
|
||||
};
|
||||
let source_type = match tex.format.has_alpha && !ct.opaque {
|
||||
true => TexSourceType::HasAlpha,
|
||||
false => TexSourceType::Opaque,
|
||||
};
|
||||
ops.push(VulkanOp::Tex(VulkanTexOp {
|
||||
tex: tex.clone(),
|
||||
range: lo..hi,
|
||||
buffer_resv: ct.buffer_resv.clone(),
|
||||
acquire_sync: ct.acquire_sync.clone(),
|
||||
release_sync: ct.release_sync,
|
||||
alpha: ct.alpha.unwrap_or_default(),
|
||||
source_type,
|
||||
copy_type,
|
||||
range_address: 0,
|
||||
instances: 0,
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
sync(memory);
|
||||
}
|
||||
|
||||
fn create_data_buffer(&self) -> Result<(), VulkanError> {
|
||||
if self.device.descriptor_buffer.is_none() {
|
||||
return Ok(());
|
||||
}
|
||||
zone!("create_data_buffer");
|
||||
let memory = &mut *self.memory.borrow_mut();
|
||||
let buf = &mut memory.data_buffer;
|
||||
{
|
||||
memory.out_address = buf.len() as _;
|
||||
for region in &memory.paint_regions[RenderPass::BlendBuffer] {
|
||||
buf.extend_from_slice(uapi::as_bytes(&[
|
||||
[region.x2, region.y1],
|
||||
[region.x1, region.y1],
|
||||
[region.x2, region.y2],
|
||||
[region.x1, region.y2],
|
||||
]));
|
||||
}
|
||||
}
|
||||
if buf.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
let buffer = self.shader_buffer_cache.allocate(buf.len() as _, 8)?;
|
||||
buffer.buffer.allocation.upload(|ptr, _| unsafe {
|
||||
ptr::copy_nonoverlapping(buf.as_ptr(), ptr, buf.len());
|
||||
})?;
|
||||
for ops in memory.ops.values_mut() {
|
||||
for op in ops {
|
||||
match op {
|
||||
VulkanOp::Fill(f) => {
|
||||
f.range_address += buffer.buffer.address;
|
||||
}
|
||||
VulkanOp::Tex(c) => {
|
||||
c.range_address += buffer.buffer.address;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
memory.out_address += buffer.buffer.address;
|
||||
memory.used_buffers.push(buffer);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn collect_memory(&self) {
|
||||
zone!("collect_memory");
|
||||
let mut memory = self.memory.borrow_mut();
|
||||
let memory = &mut *self.memory.borrow_mut();
|
||||
memory.dmabuf_sample.clear();
|
||||
memory.queue_transfer.clear();
|
||||
let execution = self.allocate_point();
|
||||
for cmd in opts {
|
||||
if let GfxApiOpt::CopyTexture(c) = cmd {
|
||||
let tex = c.tex.clone().into_vk(&self.device.device);
|
||||
if tex.contents_are_undefined.get() {
|
||||
continue;
|
||||
for pass in RenderPass::variants() {
|
||||
for cmd in &memory.ops[pass] {
|
||||
if let VulkanOp::Tex(c) = cmd {
|
||||
let tex = &c.tex;
|
||||
if tex.execution_version.replace(execution) == execution {
|
||||
continue;
|
||||
}
|
||||
match tex.queue_state.get().acquire(QueueFamily::Gfx) {
|
||||
QueueTransfer::Unnecessary => {}
|
||||
QueueTransfer::Possible => memory.queue_transfer.push(tex.clone()),
|
||||
QueueTransfer::Impossible => continue,
|
||||
}
|
||||
if let VulkanImageMemory::DmaBuf(_) = &tex.ty {
|
||||
memory.dmabuf_sample.push(tex.clone())
|
||||
}
|
||||
memory.textures.push(UsedTexture {
|
||||
tex: tex.clone(),
|
||||
resv: c.buffer_resv.clone(),
|
||||
acquire_sync: c.acquire_sync.clone(),
|
||||
release_sync: c.release_sync,
|
||||
});
|
||||
}
|
||||
if tex.execution_version.replace(execution) == execution {
|
||||
continue;
|
||||
}
|
||||
match tex.queue_state.get().acquire(QueueFamily::Gfx) {
|
||||
QueueTransfer::Unnecessary => {}
|
||||
QueueTransfer::Possible => memory.queue_transfer.push(tex.clone()),
|
||||
QueueTransfer::Impossible => continue,
|
||||
}
|
||||
if let VulkanImageMemory::DmaBuf(_) = &tex.ty {
|
||||
memory.dmabuf_sample.push(tex.clone())
|
||||
}
|
||||
memory.textures.push(UsedTexture {
|
||||
tex,
|
||||
resv: c.buffer_resv.clone(),
|
||||
acquire_sync: c.acquire_sync.clone(),
|
||||
release_sync: c.release_sync,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -571,8 +853,8 @@ impl VulkanRenderer {
|
|||
let clear_value = ClearValue {
|
||||
color: ClearColorValue {
|
||||
float32: match pass {
|
||||
RenderPass::BlendBuffer => clear.to_array_linear(None),
|
||||
RenderPass::FrameBuffer => clear.to_array_srgb(None),
|
||||
RenderPass::BlendBuffer => clear.to_array(TransferFunction::Linear),
|
||||
RenderPass::FrameBuffer => clear.to_array(TransferFunction::Srgb),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -662,12 +944,10 @@ impl VulkanRenderer {
|
|||
&self,
|
||||
buf: CommandBuffer,
|
||||
target: &VulkanImage,
|
||||
opts: &[GfxApiOpt],
|
||||
pass: RenderPass,
|
||||
) -> Result<(), VulkanError> {
|
||||
zone!("record_draws");
|
||||
let memory = &*self.memory.borrow();
|
||||
let paint_regions = &memory.paint_regions[pass];
|
||||
let pipelines = self.get_or_create_pipelines(target.format.vk_format, pass)?;
|
||||
let dev = &self.device.device;
|
||||
let mut current_pipeline = None;
|
||||
|
|
@ -679,29 +959,18 @@ impl VulkanRenderer {
|
|||
}
|
||||
}
|
||||
};
|
||||
for opt in opts {
|
||||
for opt in &memory.ops[pass] {
|
||||
match opt {
|
||||
GfxApiOpt::Sync => {}
|
||||
GfxApiOpt::FillRect(r) => {
|
||||
let push = FillPushConstants {
|
||||
pos: r.rect.to_points(),
|
||||
color: match pass {
|
||||
RenderPass::BlendBuffer => r.color.to_array_linear(r.alpha),
|
||||
RenderPass::FrameBuffer => r.color.to_array_srgb(r.alpha),
|
||||
},
|
||||
};
|
||||
let source_type = match push.color[3] < 1.0 {
|
||||
true => TexSourceType::HasAlpha,
|
||||
false => TexSourceType::Opaque,
|
||||
};
|
||||
let pipeline = &pipelines.fill[source_type];
|
||||
for region in paint_regions {
|
||||
let mut push = push;
|
||||
let draw = region.constrain(&mut push.pos, None);
|
||||
if !draw {
|
||||
continue;
|
||||
}
|
||||
bind(pipeline);
|
||||
VulkanOp::Fill(r) => {
|
||||
let pipeline = &pipelines.fill[r.source_type];
|
||||
bind(pipeline);
|
||||
if self.device.descriptor_buffer.is_some() {
|
||||
let push = FillPushConstants {
|
||||
color: r.color,
|
||||
vertices: r.range_address,
|
||||
_padding1: 0,
|
||||
_padding2: 0,
|
||||
};
|
||||
unsafe {
|
||||
dev.cmd_push_constants(
|
||||
buf,
|
||||
|
|
@ -710,70 +979,48 @@ impl VulkanRenderer {
|
|||
0,
|
||||
uapi::as_bytes(&push),
|
||||
);
|
||||
dev.cmd_draw(buf, 4, 1, 0, 0);
|
||||
dev.cmd_draw(buf, 4, r.instances, 0, 0);
|
||||
}
|
||||
} else {
|
||||
for &pos in &memory.fill_targets[r.range.clone()] {
|
||||
let push = LegacyFillPushConstants {
|
||||
pos,
|
||||
color: r.color,
|
||||
};
|
||||
unsafe {
|
||||
dev.cmd_push_constants(
|
||||
buf,
|
||||
pipeline.pipeline_layout,
|
||||
ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT,
|
||||
0,
|
||||
uapi::as_bytes(&push),
|
||||
);
|
||||
dev.cmd_draw(buf, 4, 1, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
GfxApiOpt::CopyTexture(c) => {
|
||||
let tex = c.tex.as_vk(&self.device.device);
|
||||
if tex.contents_are_undefined.get() {
|
||||
log::warn!("Ignoring undefined texture");
|
||||
continue;
|
||||
}
|
||||
if tex.queue_state.get().acquire(QueueFamily::Gfx) == QueueTransfer::Impossible
|
||||
{
|
||||
log::warn!("Ignoring texture owned by different queue");
|
||||
continue;
|
||||
}
|
||||
let copy_type = match c.alpha.is_some() {
|
||||
true => TexCopyType::Multiply,
|
||||
false => TexCopyType::Identity,
|
||||
};
|
||||
let source_type = match tex.format.has_alpha && !c.opaque {
|
||||
true => TexSourceType::HasAlpha,
|
||||
false => TexSourceType::Opaque,
|
||||
};
|
||||
let pipeline = &pipelines.tex[copy_type][source_type];
|
||||
let push = TexPushConstants {
|
||||
pos: c.target.to_points(),
|
||||
tex_pos: c.source.to_points(),
|
||||
alpha: c.alpha.unwrap_or_default(),
|
||||
};
|
||||
VulkanOp::Tex(c) => {
|
||||
let tex = &c.tex;
|
||||
let pipeline = &pipelines.tex[c.copy_type][c.source_type];
|
||||
bind(pipeline);
|
||||
let image_info = DescriptorImageInfo::default()
|
||||
.image_view(tex.texture_view)
|
||||
.image_layout(ImageLayout::SHADER_READ_ONLY_OPTIMAL);
|
||||
let init = Once::default();
|
||||
for region in paint_regions {
|
||||
let mut push = push;
|
||||
let draw = region.constrain(&mut push.pos, Some(&mut push.tex_pos));
|
||||
if !draw {
|
||||
continue;
|
||||
}
|
||||
init.exec(|| unsafe {
|
||||
bind(pipeline);
|
||||
if let Some(db) = &self.device.descriptor_buffer {
|
||||
db.cmd_set_descriptor_buffer_offsets(
|
||||
buf,
|
||||
PipelineBindPoint::GRAPHICS,
|
||||
pipeline.pipeline_layout,
|
||||
0,
|
||||
&[0],
|
||||
&[tex.descriptor_buffer_offset.get()],
|
||||
);
|
||||
} else {
|
||||
let write_descriptor_set = WriteDescriptorSet::default()
|
||||
.descriptor_type(DescriptorType::COMBINED_IMAGE_SAMPLER)
|
||||
.image_info(slice::from_ref(&image_info));
|
||||
self.device.push_descriptor.cmd_push_descriptor_set(
|
||||
buf,
|
||||
PipelineBindPoint::GRAPHICS,
|
||||
pipeline.pipeline_layout,
|
||||
0,
|
||||
slice::from_ref(&write_descriptor_set),
|
||||
);
|
||||
}
|
||||
});
|
||||
if let Some(db) = &self.device.descriptor_buffer {
|
||||
let push = TexPushConstants {
|
||||
vertices: c.range_address,
|
||||
alpha: c.alpha,
|
||||
};
|
||||
unsafe {
|
||||
db.cmd_set_descriptor_buffer_offsets(
|
||||
buf,
|
||||
PipelineBindPoint::GRAPHICS,
|
||||
pipeline.pipeline_layout,
|
||||
0,
|
||||
&[0],
|
||||
&[tex.descriptor_buffer_offset.get()],
|
||||
);
|
||||
dev.cmd_push_constants(
|
||||
buf,
|
||||
pipeline.pipeline_layout,
|
||||
|
|
@ -781,7 +1028,37 @@ impl VulkanRenderer {
|
|||
0,
|
||||
uapi::as_bytes(&push),
|
||||
);
|
||||
dev.cmd_draw(buf, 4, 1, 0, 0);
|
||||
dev.cmd_draw(buf, 4, c.instances, 0, 0);
|
||||
}
|
||||
} else {
|
||||
let write_descriptor_set = WriteDescriptorSet::default()
|
||||
.descriptor_type(DescriptorType::COMBINED_IMAGE_SAMPLER)
|
||||
.image_info(slice::from_ref(&image_info));
|
||||
unsafe {
|
||||
self.device.push_descriptor.cmd_push_descriptor_set(
|
||||
buf,
|
||||
PipelineBindPoint::GRAPHICS,
|
||||
pipeline.pipeline_layout,
|
||||
0,
|
||||
slice::from_ref(&write_descriptor_set),
|
||||
);
|
||||
}
|
||||
for &[pos, tex_pos] in &memory.tex_targets[c.range.clone()] {
|
||||
let push = LegacyTexPushConstants {
|
||||
pos,
|
||||
tex_pos,
|
||||
alpha: c.alpha,
|
||||
};
|
||||
unsafe {
|
||||
dev.cmd_push_constants(
|
||||
buf,
|
||||
pipeline.pipeline_layout,
|
||||
ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT,
|
||||
0,
|
||||
uapi::as_bytes(&push),
|
||||
);
|
||||
dev.cmd_draw(buf, 4, 1, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -835,13 +1112,18 @@ impl VulkanRenderer {
|
|||
blend: false,
|
||||
src_has_alpha: true,
|
||||
has_alpha_mult: false,
|
||||
with_linear_output: true,
|
||||
eotf: TF_LINEAR,
|
||||
oetf: TF_SRGB,
|
||||
frag_descriptor_set_layout: Some(layout.clone()),
|
||||
})?;
|
||||
e.insert(out.clone());
|
||||
out
|
||||
}
|
||||
};
|
||||
let push = OutPushConstants {
|
||||
vertices: memory.out_address,
|
||||
};
|
||||
let instances = memory.paint_regions[RenderPass::BlendBuffer].len() as u32;
|
||||
let dev = &self.device.device;
|
||||
unsafe {
|
||||
dev.cmd_bind_pipeline(buf, PipelineBindPoint::GRAPHICS, pipeline.pipeline);
|
||||
|
|
@ -853,26 +1135,14 @@ impl VulkanRenderer {
|
|||
&[1],
|
||||
&[bb.descriptor_buffer_offset.get()],
|
||||
);
|
||||
}
|
||||
for region in &memory.paint_regions[RenderPass::BlendBuffer] {
|
||||
let push = OutPushConstants {
|
||||
pos: [
|
||||
[region.x2, region.y1],
|
||||
[region.x1, region.y1],
|
||||
[region.x2, region.y2],
|
||||
[region.x1, region.y2],
|
||||
],
|
||||
};
|
||||
unsafe {
|
||||
dev.cmd_push_constants(
|
||||
buf,
|
||||
pipeline.pipeline_layout,
|
||||
ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT,
|
||||
0,
|
||||
uapi::as_bytes(&push),
|
||||
);
|
||||
dev.cmd_draw(buf, 4, 1, 0, 0);
|
||||
}
|
||||
dev.cmd_push_constants(
|
||||
buf,
|
||||
pipeline.pipeline_layout,
|
||||
ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT,
|
||||
0,
|
||||
uapi::as_bytes(&push),
|
||||
);
|
||||
dev.cmd_draw(buf, 4, instances, 0, 0);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -1215,7 +1485,7 @@ impl VulkanRenderer {
|
|||
wait_semaphores: Cell::new(mem::take(&mut memory.wait_semaphores)),
|
||||
waiter: Cell::new(None),
|
||||
_release_fence: memory.release_fence.take(),
|
||||
_descriptor_buffers: mem::take(&mut memory.descriptor_buffers),
|
||||
_used_buffers: mem::take(&mut memory.used_buffers),
|
||||
});
|
||||
self.pending_frames.set(frame.point, frame.clone());
|
||||
let future = self.eng.spawn(
|
||||
|
|
@ -1257,7 +1527,7 @@ impl VulkanRenderer {
|
|||
memory.queue_transfer.clear();
|
||||
memory.wait_semaphores.clear();
|
||||
memory.release_fence.take();
|
||||
memory.descriptor_buffers.clear();
|
||||
memory.used_buffers.clear();
|
||||
memory.release_sync_file.take()
|
||||
};
|
||||
res.map(|_| sync_file)
|
||||
|
|
@ -1290,7 +1560,7 @@ impl VulkanRenderer {
|
|||
for opt in opts.iter().rev() {
|
||||
let (opaque, fb_rect) = match opt {
|
||||
GfxApiOpt::Sync => continue,
|
||||
GfxApiOpt::FillRect(f) => (f.effective_color().a >= 1.0, f.rect),
|
||||
GfxApiOpt::FillRect(f) => (f.effective_color().is_opaque(), f.rect),
|
||||
GfxApiOpt::CopyTexture(c) => {
|
||||
let opaque = 'opaque: {
|
||||
if let Some(a) = c.alpha {
|
||||
|
|
@ -1349,6 +1619,18 @@ impl VulkanRenderer {
|
|||
y2: to_fb(y2, fb.height),
|
||||
});
|
||||
}
|
||||
for pass in RenderPass::variants() {
|
||||
let regions = &memory.paint_regions[pass];
|
||||
if regions.is_empty() {
|
||||
memory.paint_bounds[pass] = None;
|
||||
} else {
|
||||
let mut union = regions[0];
|
||||
for region in ®ions[1..] {
|
||||
union = union.union(region);
|
||||
}
|
||||
memory.paint_bounds[pass] = Some(union);
|
||||
}
|
||||
}
|
||||
let blend_clear = clear_region.intersect(&Region::from_rects2(&memory.regions_1));
|
||||
let opaque_clear = clear_region.subtract_cow(&blend_clear);
|
||||
// if bb.is_none() {
|
||||
|
|
@ -1407,23 +1689,25 @@ impl VulkanRenderer {
|
|||
self.elide_blend_buffer(&mut blend_buffer);
|
||||
let bb = blend_buffer.as_deref();
|
||||
let buf = self.gfx_command_buffers.allocate()?;
|
||||
self.collect_memory(opts);
|
||||
self.convert_ops(opts);
|
||||
self.create_data_buffer()?;
|
||||
self.collect_memory();
|
||||
self.begin_command_buffer(buf.buffer)?;
|
||||
self.create_descriptor_buffers(buf.buffer, opts, bb)?;
|
||||
self.create_descriptor_buffers(buf.buffer, bb)?;
|
||||
self.initial_barriers(buf.buffer, fb)?;
|
||||
self.set_viewport(buf.buffer, fb);
|
||||
if let Some(bb) = bb {
|
||||
zone!("blend buffer pass");
|
||||
self.blend_buffer_initial_barrier(buf.buffer, bb);
|
||||
self.begin_rendering(buf.buffer, bb, clear, RenderPass::BlendBuffer);
|
||||
self.record_draws(buf.buffer, bb, opts, RenderPass::BlendBuffer)?;
|
||||
self.record_draws(buf.buffer, bb, RenderPass::BlendBuffer)?;
|
||||
self.end_rendering(buf.buffer);
|
||||
self.blend_buffer_final_barrier(buf.buffer, bb);
|
||||
}
|
||||
{
|
||||
zone!("frame buffer pass");
|
||||
self.begin_rendering(buf.buffer, fb, clear, RenderPass::FrameBuffer);
|
||||
self.record_draws(buf.buffer, fb, opts, RenderPass::FrameBuffer)?;
|
||||
self.record_draws(buf.buffer, fb, RenderPass::FrameBuffer)?;
|
||||
if let Some(bb) = bb {
|
||||
self.blend_buffer_copy(buf.buffer, fb, bb)?;
|
||||
}
|
||||
|
|
@ -1558,7 +1842,31 @@ async fn await_release(
|
|||
}
|
||||
|
||||
impl PaintRegion {
|
||||
fn constrain(&self, pos: &mut [[f32; 2]; 4], tex_pos: Option<&mut [[f32; 2]; 4]>) -> bool {
|
||||
fn intersects(&self, pos: &Point) -> bool {
|
||||
let mut p = *pos;
|
||||
for [x, y] in &mut p {
|
||||
*x = x.clamp(self.x1, self.x2);
|
||||
*y = y.clamp(self.y1, self.y2);
|
||||
}
|
||||
if p[0] == p[1] && p[2] == p[3] {
|
||||
return false;
|
||||
}
|
||||
if p[0] == p[2] && p[1] == p[3] {
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn union(&self, other: &Self) -> Self {
|
||||
Self {
|
||||
x1: self.x1.min(other.x1),
|
||||
y1: self.y1.min(other.y1),
|
||||
x2: self.x2.max(other.x2),
|
||||
y2: self.y2.max(other.y2),
|
||||
}
|
||||
}
|
||||
|
||||
fn constrain(&self, pos: &mut Point, tex_pos: Option<&mut Point>) -> bool {
|
||||
zone!("constrain");
|
||||
let mut npos = *pos;
|
||||
for [x, y] in &mut npos {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use {
|
||||
crate::gfx_apis::vulkan::{VulkanError, device::VulkanDevice},
|
||||
ash::vk::{ShaderModule, ShaderModuleCreateInfo},
|
||||
ash::vk::{DeviceAddress, ShaderModule, ShaderModuleCreateInfo},
|
||||
std::rc::Rc,
|
||||
uapi::Packed,
|
||||
};
|
||||
|
|
@ -11,6 +11,12 @@ pub const TEX_VERT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/tex.vert.s
|
|||
pub const TEX_FRAG: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/tex.frag.spv"));
|
||||
pub const OUT_VERT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/out.vert.spv"));
|
||||
pub const OUT_FRAG: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/out.frag.spv"));
|
||||
pub const LEGACY_FILL_VERT: &[u8] =
|
||||
include_bytes!(concat!(env!("OUT_DIR"), "/legacy_fill.vert.spv"));
|
||||
pub const LEGACY_FILL_FRAG: &[u8] =
|
||||
include_bytes!(concat!(env!("OUT_DIR"), "/legacy_fill.frag.spv"));
|
||||
pub const LEGACY_TEX_VERT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/legacy_tex.vert.spv"));
|
||||
pub const LEGACY_TEX_FRAG: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/legacy_tex.frag.spv"));
|
||||
|
||||
pub struct VulkanShader {
|
||||
pub(super) device: Rc<VulkanDevice>,
|
||||
|
|
@ -20,17 +26,36 @@ pub struct VulkanShader {
|
|||
#[derive(Copy, Clone, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct FillPushConstants {
|
||||
pub pos: [[f32; 2]; 4],
|
||||
pub color: [f32; 4],
|
||||
pub vertices: DeviceAddress,
|
||||
pub _padding1: u32,
|
||||
pub _padding2: u32,
|
||||
}
|
||||
|
||||
unsafe impl Packed for FillPushConstants {}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct TexPushConstants {
|
||||
pub struct LegacyFillPushConstants {
|
||||
pub pos: [[f32; 2]; 4],
|
||||
pub color: [f32; 4],
|
||||
}
|
||||
|
||||
unsafe impl Packed for LegacyFillPushConstants {}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct TexVertex {
|
||||
pub pos: [[f32; 2]; 4],
|
||||
pub tex_pos: [[f32; 2]; 4],
|
||||
}
|
||||
|
||||
unsafe impl Packed for TexVertex {}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct TexPushConstants {
|
||||
pub vertices: DeviceAddress,
|
||||
pub alpha: f32,
|
||||
}
|
||||
|
||||
|
|
@ -38,8 +63,18 @@ unsafe impl Packed for TexPushConstants {}
|
|||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct OutPushConstants {
|
||||
pub struct LegacyTexPushConstants {
|
||||
pub pos: [[f32; 2]; 4],
|
||||
pub tex_pos: [[f32; 2]; 4],
|
||||
pub alpha: f32,
|
||||
}
|
||||
|
||||
unsafe impl Packed for LegacyTexPushConstants {}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct OutPushConstants {
|
||||
pub vertices: DeviceAddress,
|
||||
}
|
||||
|
||||
unsafe impl Packed for OutPushConstants {}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,12 @@
|
|||
#extension GL_EXT_buffer_reference : require
|
||||
|
||||
layout(buffer_reference, buffer_reference_align = 8, std430) buffer Vertices {
|
||||
vec2 pos[][4];
|
||||
};
|
||||
|
||||
layout(push_constant, std430) uniform Data {
|
||||
layout(offset = 0) vec2 pos[4];
|
||||
layout(offset = 32) vec4 color;
|
||||
vec4 color;
|
||||
Vertices vertices;
|
||||
uint padding1;
|
||||
uint padding2;
|
||||
} data;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
#version 450
|
||||
|
||||
#include "frag_spec_const.glsl"
|
||||
#include "fill.common.glsl"
|
||||
|
||||
layout(location = 0) out vec4 out_color;
|
||||
|
|
|
|||
|
|
@ -1,16 +1,8 @@
|
|||
#version 450
|
||||
//#extension GL_EXT_debug_printf : enable
|
||||
|
||||
#include "fill.common.glsl"
|
||||
|
||||
void main() {
|
||||
vec2 pos;
|
||||
switch (gl_VertexIndex) {
|
||||
case 0: pos = data.pos[0]; break;
|
||||
case 1: pos = data.pos[1]; break;
|
||||
case 2: pos = data.pos[2]; break;
|
||||
case 3: pos = data.pos[3]; break;
|
||||
}
|
||||
vec2 pos = data.vertices.pos[gl_InstanceIndex][gl_VertexIndex];
|
||||
gl_Position = vec4(pos, 0.0, 1.0);
|
||||
// debugPrintfEXT("gl_Position = %v4f", gl_Position);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
layout(constant_id = 0) const bool src_has_alpha = false;
|
||||
layout(constant_id = 1) const bool has_alpha_multiplier = false;
|
||||
layout(constant_id = 2) const bool color_management = false;
|
||||
layout(constant_id = 2) const uint eotf = 0;
|
||||
layout(constant_id = 3) const uint oetf = 0;
|
||||
|
||||
#endif
|
||||
|
|
|
|||
4
src/gfx_apis/vulkan/shaders/legacy/fill.common.glsl
Normal file
4
src/gfx_apis/vulkan/shaders/legacy/fill.common.glsl
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
layout(push_constant, std430) uniform Data {
|
||||
layout(offset = 0) vec2 pos[4];
|
||||
layout(offset = 32) vec4 color;
|
||||
} data;
|
||||
10
src/gfx_apis/vulkan/shaders/legacy/fill.frag
Normal file
10
src/gfx_apis/vulkan/shaders/legacy/fill.frag
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
#version 450
|
||||
|
||||
#include "../frag_spec_const.glsl"
|
||||
#include "fill.common.glsl"
|
||||
|
||||
layout(location = 0) out vec4 out_color;
|
||||
|
||||
void main() {
|
||||
out_color = data.color;
|
||||
}
|
||||
16
src/gfx_apis/vulkan/shaders/legacy/fill.vert
Normal file
16
src/gfx_apis/vulkan/shaders/legacy/fill.vert
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#version 450
|
||||
//#extension GL_EXT_debug_printf : enable
|
||||
|
||||
#include "fill.common.glsl"
|
||||
|
||||
void main() {
|
||||
vec2 pos;
|
||||
switch (gl_VertexIndex) {
|
||||
case 0: pos = data.pos[0]; break;
|
||||
case 1: pos = data.pos[1]; break;
|
||||
case 2: pos = data.pos[2]; break;
|
||||
case 3: pos = data.pos[3]; break;
|
||||
}
|
||||
gl_Position = vec4(pos, 0.0, 1.0);
|
||||
// debugPrintfEXT("gl_Position = %v4f", gl_Position);
|
||||
}
|
||||
5
src/gfx_apis/vulkan/shaders/legacy/tex.common.glsl
Normal file
5
src/gfx_apis/vulkan/shaders/legacy/tex.common.glsl
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
layout(push_constant, std430) uniform Data {
|
||||
layout(offset = 0) vec2 pos[4];
|
||||
layout(offset = 32) vec2 tex_pos[4];
|
||||
layout(offset = 64) float mul;
|
||||
} data;
|
||||
20
src/gfx_apis/vulkan/shaders/legacy/tex.frag
Normal file
20
src/gfx_apis/vulkan/shaders/legacy/tex.frag
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
#version 450
|
||||
|
||||
#include "../frag_spec_const.glsl"
|
||||
#include "tex.common.glsl"
|
||||
|
||||
layout(set = 0, binding = 0) uniform sampler2D tex;
|
||||
layout(location = 0) in vec2 tex_pos;
|
||||
layout(location = 0) out vec4 out_color;
|
||||
|
||||
void main() {
|
||||
vec4 c = textureLod(tex, tex_pos, 0);
|
||||
if (has_alpha_multiplier) {
|
||||
if (src_has_alpha) {
|
||||
c *= data.mul;
|
||||
} else {
|
||||
c = vec4(c.rgb * data.mul, data.mul);
|
||||
}
|
||||
}
|
||||
out_color = c;
|
||||
}
|
||||
18
src/gfx_apis/vulkan/shaders/legacy/tex.vert
Normal file
18
src/gfx_apis/vulkan/shaders/legacy/tex.vert
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
#version 450
|
||||
//#extension GL_EXT_debug_printf : enable
|
||||
|
||||
#include "tex.common.glsl"
|
||||
|
||||
layout(location = 0) out vec2 tex_pos;
|
||||
|
||||
void main() {
|
||||
vec2 pos;
|
||||
switch (gl_VertexIndex) {
|
||||
case 0: pos = data.pos[0]; tex_pos = data.tex_pos[0]; break;
|
||||
case 1: pos = data.pos[1]; tex_pos = data.tex_pos[1]; break;
|
||||
case 2: pos = data.pos[2]; tex_pos = data.tex_pos[2]; break;
|
||||
case 3: pos = data.pos[3]; tex_pos = data.tex_pos[3]; break;
|
||||
}
|
||||
gl_Position = vec4(pos, 0.0, 1.0);
|
||||
// debugPrintfEXT("gl_Position = %v4f, tex_pos = %v2f", gl_Position, tex_pos);
|
||||
}
|
||||
|
|
@ -1,3 +1,9 @@
|
|||
#extension GL_EXT_buffer_reference : require
|
||||
|
||||
layout(buffer_reference, buffer_reference_align = 8, std430) buffer Vertices {
|
||||
vec2 pos[][4];
|
||||
};
|
||||
|
||||
layout(push_constant, std430) uniform Data {
|
||||
layout(offset = 0) vec2 pos[4];
|
||||
Vertices vertices;
|
||||
} data;
|
||||
|
|
|
|||
|
|
@ -10,8 +10,11 @@ layout(location = 0) out vec4 out_color;
|
|||
|
||||
void main() {
|
||||
vec4 c = texelFetch(in_color, ivec2(gl_FragCoord.xy), 0);
|
||||
c.rgb /= mix(c.a, 1.0, c.a == 0.0);
|
||||
c.rgb = oetf_srgb(c.rgb);
|
||||
c.rgb *= c.a;
|
||||
if (eotf != oetf) {
|
||||
c.rgb /= mix(c.a, 1.0, c.a == 0.0);
|
||||
c.rgb = apply_eotf(c.rgb);
|
||||
c.rgb = apply_oetf(c.rgb);
|
||||
c.rgb *= c.a;
|
||||
}
|
||||
out_color = c;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,8 @@
|
|||
#version 450
|
||||
//#extension GL_EXT_debug_printf : enable
|
||||
|
||||
#include "out.common.glsl"
|
||||
|
||||
void main() {
|
||||
vec2 pos;
|
||||
switch (gl_VertexIndex) {
|
||||
case 0: pos = data.pos[0]; break;
|
||||
case 1: pos = data.pos[1]; break;
|
||||
case 2: pos = data.pos[2]; break;
|
||||
case 3: pos = data.pos[3]; break;
|
||||
}
|
||||
vec2 pos = data.vertices.pos[gl_InstanceIndex][gl_VertexIndex];
|
||||
gl_Position = vec4(pos, 0.0, 1.0);
|
||||
// debugPrintfEXT("X gl_Position = %v4f, pos = %v2f", gl_Position, pos);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,15 @@
|
|||
#extension GL_EXT_buffer_reference : require
|
||||
|
||||
struct Vertex {
|
||||
vec2 pos[4];
|
||||
vec2 tex_pos[4];
|
||||
};
|
||||
|
||||
layout(buffer_reference, buffer_reference_align = 8, std430) buffer Vertices {
|
||||
Vertex vertices[];
|
||||
};
|
||||
|
||||
layout(push_constant, std430) uniform Data {
|
||||
layout(offset = 0) vec2 pos[4];
|
||||
layout(offset = 32) vec2 tex_pos[4];
|
||||
layout(offset = 64) float mul;
|
||||
Vertices vertices;
|
||||
float mul;
|
||||
} data;
|
||||
|
|
|
|||
|
|
@ -10,11 +10,12 @@ layout(location = 0) out vec4 out_color;
|
|||
|
||||
void main() {
|
||||
vec4 c = textureLod(tex, tex_pos, 0);
|
||||
if (color_management) {
|
||||
if (eotf != oetf) {
|
||||
if (src_has_alpha) {
|
||||
c.rgb /= mix(c.a, 1.0, c.a == 0.0);
|
||||
}
|
||||
c.rgb = eotf_srgb(c.rgb);
|
||||
c.rgb = apply_eotf(c.rgb);
|
||||
c.rgb = apply_oetf(c.rgb);
|
||||
if (src_has_alpha) {
|
||||
c.rgb *= c.a;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,11 @@
|
|||
#version 450
|
||||
//#extension GL_EXT_debug_printf : enable
|
||||
|
||||
#include "tex.common.glsl"
|
||||
|
||||
layout(location = 0) out vec2 tex_pos;
|
||||
|
||||
void main() {
|
||||
vec2 pos;
|
||||
switch (gl_VertexIndex) {
|
||||
case 0: pos = data.pos[0]; tex_pos = data.tex_pos[0]; break;
|
||||
case 1: pos = data.pos[1]; tex_pos = data.tex_pos[1]; break;
|
||||
case 2: pos = data.pos[2]; tex_pos = data.tex_pos[2]; break;
|
||||
case 3: pos = data.pos[3]; tex_pos = data.tex_pos[3]; break;
|
||||
}
|
||||
gl_Position = vec4(pos, 0.0, 1.0);
|
||||
// debugPrintfEXT("gl_Position = %v4f, tex_pos = %v2f", gl_Position, tex_pos);
|
||||
Vertex vertex = data.vertices.vertices[gl_InstanceIndex];
|
||||
gl_Position = vec4(vertex.pos[gl_VertexIndex], 0.0, 1.0);
|
||||
tex_pos = vertex.tex_pos[gl_VertexIndex];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
#ifndef TRANSFER_FUNCTIONS_GLSL
|
||||
#define TRANSFER_FUNCTIONS_GLSL
|
||||
|
||||
#include "frag_spec_const.glsl"
|
||||
|
||||
#define SRGB 0
|
||||
#define LINEAR 1
|
||||
|
||||
vec3 eotf_srgb(vec3 c) {
|
||||
return mix(
|
||||
c * vec3(1.0 / 12.92),
|
||||
|
|
@ -18,4 +23,20 @@ vec3 oetf_srgb(vec3 c) {
|
|||
);
|
||||
}
|
||||
|
||||
vec3 apply_eotf(vec3 c) {
|
||||
switch (eotf) {
|
||||
case SRGB: return eotf_srgb(c);
|
||||
case LINEAR: return c;
|
||||
default: return c;
|
||||
}
|
||||
}
|
||||
|
||||
vec3 apply_oetf(vec3 c) {
|
||||
switch (oetf) {
|
||||
case SRGB: return oetf_srgb(c);
|
||||
case LINEAR: return c;
|
||||
default: return c;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -77,6 +77,9 @@ impl VulkanShmImage {
|
|||
callback: Rc<dyn AsyncShmGfxTextureCallback>,
|
||||
tt: TransferType,
|
||||
) -> Result<Option<PendingShmTransfer>, VulkanError> {
|
||||
if damage.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
let data = self.async_data.as_ref().unwrap();
|
||||
let res = self.try_async_transfer(img, staging, data, client_mem, damage, tt);
|
||||
match res {
|
||||
|
|
|
|||
2
src/gfx_apis/vulkan/transfer_functions.rs
Normal file
2
src/gfx_apis/vulkan/transfer_functions.rs
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
pub const TF_SRGB: u32 = 0;
|
||||
pub const TF_LINEAR: u32 = 1;
|
||||
|
|
@ -4,7 +4,7 @@ use {
|
|||
globals::{Global, GlobalName},
|
||||
leaks::Tracker,
|
||||
object::{Object, Version},
|
||||
theme::Color,
|
||||
theme::{Color, TransferFunction},
|
||||
wire::{
|
||||
JayCompositorId,
|
||||
jay_damage_tracking::{
|
||||
|
|
@ -96,12 +96,8 @@ impl JayDamageTrackingRequestHandler for JayDamageTracking {
|
|||
req: SetVisualizerColor,
|
||||
_slf: &Rc<Self>,
|
||||
) -> Result<(), Self::Error> {
|
||||
self.client.state.damage_visualizer.set_color(Color {
|
||||
r: req.r,
|
||||
g: req.g,
|
||||
b: req.b,
|
||||
a: req.a,
|
||||
});
|
||||
let color = Color::new(TransferFunction::Srgb, req.r, req.g, req.b) * req.a;
|
||||
self.client.state.damage_visualizer.set_color(color);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ use {
|
|||
leaks::Tracker,
|
||||
object::{Object, Version},
|
||||
rect::{Rect, Region},
|
||||
theme::Color,
|
||||
utils::errorfmt::ErrorFmt,
|
||||
video::dmabuf::DmaBuf,
|
||||
wire::{WlBufferId, wl_buffer::*},
|
||||
|
|
@ -42,7 +41,7 @@ pub struct WlBuffer {
|
|||
render_ctx_version: Cell<u32>,
|
||||
pub storage: RefCell<Option<WlBufferStorage>>,
|
||||
shm: bool,
|
||||
pub color: Option<Color>,
|
||||
pub color: Option<[u32; 4]>,
|
||||
width: i32,
|
||||
height: i32,
|
||||
pub tracker: Tracker<Self>,
|
||||
|
|
@ -149,7 +148,7 @@ impl WlBuffer {
|
|||
width: 1,
|
||||
height: 1,
|
||||
tracker: Default::default(),
|
||||
color: Some(Color::from_u32_rgba_premultiplied(r, g, b, a)),
|
||||
color: Some([r, g, b, a]),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -435,7 +435,7 @@ impl GfxFramebuffer for TestGfxFb {
|
|||
a = 255;
|
||||
}
|
||||
staging[(y * width + x) as usize] =
|
||||
Color::from_rgba_premultiplied(r, g, b, a);
|
||||
Color::from_srgba_premultiplied(r, g, b, a);
|
||||
}
|
||||
data = data.add(stride as usize);
|
||||
}
|
||||
|
|
@ -446,7 +446,7 @@ impl GfxFramebuffer for TestGfxFb {
|
|||
for y in 0..height {
|
||||
for x in 0..width {
|
||||
let [r, g, b, a] =
|
||||
staging[(y * width + x) as usize].to_rgba_premultiplied();
|
||||
staging[(y * width + x) as usize].to_srgba_premultiplied();
|
||||
*data.add((x * 4) as usize).cast::<[u8; 4]>() = [b, g, r, a];
|
||||
}
|
||||
data = data.add(stride as usize);
|
||||
|
|
@ -499,7 +499,7 @@ impl GfxFramebuffer for TestGfxFb {
|
|||
if !t_format.has_alpha {
|
||||
a = 255;
|
||||
}
|
||||
let mut color = Color::from_rgba_premultiplied(r, g, b, a);
|
||||
let mut color = Color::from_srgba_premultiplied(r, g, b, a);
|
||||
if let Some(alpha) = c.alpha {
|
||||
color = color * alpha;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ pub struct TestShmBuffer {
|
|||
|
||||
impl TestShmBuffer {
|
||||
pub fn fill(&self, color: Color) {
|
||||
let [cr, cg, cb, ca] = color.to_rgba_premultiplied();
|
||||
let [cr, cg, cb, ca] = color.to_srgba_premultiplied();
|
||||
for [b, g, r, a] in self.deref().array_chunks_ext::<4>() {
|
||||
r.set(cr);
|
||||
g.set(cg);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use {
|
|||
test_error::TestResult, test_ifs::test_buffer::TestBuffer, test_object::TestObject,
|
||||
test_transport::TestTransport,
|
||||
},
|
||||
theme::Color,
|
||||
theme::{Color, TransferFunction},
|
||||
wire::{WpSinglePixelBufferManagerV1Id, wp_single_pixel_buffer_manager_v1::*},
|
||||
},
|
||||
std::{cell::Cell, rc::Rc},
|
||||
|
|
@ -31,13 +31,14 @@ impl TestSinglePixelBufferManager {
|
|||
destroyed: Cell::new(false),
|
||||
});
|
||||
let map = |c: f32| (c as f64 * u32::MAX as f64) as u32;
|
||||
let [r, g, b, a] = color.to_array(TransferFunction::Srgb);
|
||||
self.tran.send(CreateU32RgbaBuffer {
|
||||
self_id: self.id,
|
||||
id: obj.id,
|
||||
r: map(color.r),
|
||||
g: map(color.g),
|
||||
b: map(color.b),
|
||||
a: map(color.a),
|
||||
r: map(r),
|
||||
g: map(g),
|
||||
b: map(b),
|
||||
a: map(a),
|
||||
})?;
|
||||
self.tran.add_obj(obj.clone())?;
|
||||
Ok(obj)
|
||||
|
|
|
|||
|
|
@ -39,6 +39,6 @@ impl TestSurfaceExt {
|
|||
}
|
||||
|
||||
pub fn set_color(&self, r: u8, g: u8, b: u8, a: u8) {
|
||||
self.color.set(Color::from_rgba_straight(r, g, b, a));
|
||||
self.color.set(Color::from_srgba_straight(r, g, b, a));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ async fn test(run: Rc<TestRun>) -> Result<(), TestError> {
|
|||
|
||||
let buffer = client
|
||||
.spbm
|
||||
.create_buffer(Color::from_rgba_straight(255, 255, 255, 255))?;
|
||||
.create_buffer(Color::from_srgba_straight(255, 255, 255, 255))?;
|
||||
|
||||
child.attach(buffer.id)?;
|
||||
child_viewport.set_source(0, 0, 1, 1)?;
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ async fn test(run: Rc<TestRun>) -> Result<(), TestError> {
|
|||
let win1 = client.create_window().await?;
|
||||
win1.map2().await?;
|
||||
|
||||
let buffer = client.spbm.create_buffer(Color::from_rgb(255, 0, 0))?;
|
||||
let buffer = client.spbm.create_buffer(Color::from_srgb(255, 0, 0))?;
|
||||
let surface = client.comp.create_surface().await?;
|
||||
let vp = client.viewporter.get_viewport(&surface)?;
|
||||
vp.set_destination(100, 100)?;
|
||||
|
|
|
|||
|
|
@ -36,11 +36,11 @@ async fn test(run: Rc<TestRun>) -> TestResult {
|
|||
}};
|
||||
}
|
||||
|
||||
let buf1 = client.spbm.create_buffer(Color::from_rgb(0, 255, 0))?;
|
||||
let buf1 = client.spbm.create_buffer(Color::from_srgb(0, 255, 0))?;
|
||||
let (ss1, alpha1) = create_surface!(&buf1, 0, 0);
|
||||
|
||||
let buf2 = client.shm.create_buffer(1, 1)?;
|
||||
buf2.fill(Color::from_rgb(0, 255, 0));
|
||||
buf2.fill(Color::from_srgb(0, 255, 0));
|
||||
let (ss2, alpha2) = create_surface!(&buf2.buffer, 100, 0);
|
||||
|
||||
client.compare_screenshot("1", false).await?;
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -67,18 +67,18 @@ fn create_accept_gui(surface: &Rc<SelectionGuiSurface>) -> Rc<dyn GuiElement> {
|
|||
let accept_button = static_button(surface, ButtonRole::Accept, "Allow");
|
||||
let reject_button = static_button(surface, ButtonRole::Reject, "Reject");
|
||||
for button in [&accept_button, &reject_button] {
|
||||
button.border_color.set(Color::from_gray(100));
|
||||
button.border_color.set(Color::from_gray_srgb(100));
|
||||
button.border.set(2.0);
|
||||
button.padding.set(5.0);
|
||||
}
|
||||
accept_button.bg_color.set(Color::from_rgb(170, 200, 170));
|
||||
accept_button.bg_color.set(Color::from_srgb(170, 200, 170));
|
||||
accept_button
|
||||
.bg_hover_color
|
||||
.set(Color::from_rgb(170, 255, 170));
|
||||
reject_button.bg_color.set(Color::from_rgb(200, 170, 170));
|
||||
.set(Color::from_srgb(170, 255, 170));
|
||||
reject_button.bg_color.set(Color::from_srgb(200, 170, 170));
|
||||
reject_button
|
||||
.bg_hover_color
|
||||
.set(Color::from_rgb(255, 170, 170));
|
||||
.set(Color::from_srgb(255, 170, 170));
|
||||
let flow = Rc::new(Flow::default());
|
||||
flow.orientation.set(Orientation::Vertical);
|
||||
flow.cross_align.set(Align::Center);
|
||||
|
|
|
|||
|
|
@ -87,22 +87,22 @@ fn create_accept_gui(surface: &Rc<SelectionGuiSurface>, for_restore: bool) -> Rc
|
|||
&window_button,
|
||||
&reject_button,
|
||||
] {
|
||||
button.border_color.set(Color::from_gray(100));
|
||||
button.border_color.set(Color::from_gray_srgb(100));
|
||||
button.border.set(2.0);
|
||||
button.padding.set(5.0);
|
||||
}
|
||||
restore_button.bg_color.set(Color::from_rgb(170, 170, 200));
|
||||
restore_button.bg_color.set(Color::from_srgb(170, 170, 200));
|
||||
restore_button
|
||||
.bg_hover_color
|
||||
.set(Color::from_rgb(170, 170, 255));
|
||||
.set(Color::from_srgb(170, 170, 255));
|
||||
for button in [&accept_button, &workspace_button, &window_button] {
|
||||
button.bg_color.set(Color::from_rgb(170, 200, 170));
|
||||
button.bg_hover_color.set(Color::from_rgb(170, 255, 170));
|
||||
button.bg_color.set(Color::from_srgb(170, 200, 170));
|
||||
button.bg_hover_color.set(Color::from_srgb(170, 255, 170));
|
||||
}
|
||||
reject_button.bg_color.set(Color::from_rgb(200, 170, 170));
|
||||
reject_button.bg_color.set(Color::from_srgb(200, 170, 170));
|
||||
reject_button
|
||||
.bg_hover_color
|
||||
.set(Color::from_rgb(255, 170, 170));
|
||||
.set(Color::from_srgb(255, 170, 170));
|
||||
let flow = Rc::new(Flow::default());
|
||||
flow.orientation.set(Orientation::Vertical);
|
||||
flow.cross_align.set(Align::Center);
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use {
|
|||
consts::{CAIRO_FORMAT_ARGB32, CAIRO_OPERATOR_SOURCE},
|
||||
},
|
||||
rect::Rect,
|
||||
theme::Color,
|
||||
theme::{Color, TransferFunction},
|
||||
},
|
||||
std::{ops::Neg, rc::Rc, sync::Arc},
|
||||
};
|
||||
|
|
@ -78,9 +78,9 @@ pub fn render(
|
|||
let data = create_data(font, width, height, scale)?;
|
||||
data.layout.set_text(text);
|
||||
let font_height = data.layout.pixel_size().1;
|
||||
let [r, g, b, a] = color.to_array(TransferFunction::Srgb);
|
||||
data.cctx.set_operator(CAIRO_OPERATOR_SOURCE);
|
||||
data.cctx
|
||||
.set_source_rgba(color.r as _, color.g as _, color.b as _, color.a as _);
|
||||
data.cctx.set_source_rgba(r as _, g as _, b as _, a as _);
|
||||
let y = y.unwrap_or((height - font_height) / 2);
|
||||
data.cctx.move_to(x as f64, y as f64);
|
||||
data.layout.show_layout();
|
||||
|
|
|
|||
|
|
@ -139,9 +139,9 @@ impl Default for Button {
|
|||
hover: Default::default(),
|
||||
padding: Default::default(),
|
||||
border: Default::default(),
|
||||
border_color: Cell::new(Color::from_gray(0)),
|
||||
bg_color: Cell::new(Color::from_gray(255)),
|
||||
bg_hover_color: Cell::new(Color::from_gray(255)),
|
||||
border_color: Cell::new(Color::from_gray_srgb(0)),
|
||||
bg_color: Cell::new(Color::from_gray_srgb(255)),
|
||||
bg_hover_color: Cell::new(Color::from_gray_srgb(255)),
|
||||
text: Default::default(),
|
||||
font: Arc::new(DEFAULT_FONT.to_string()),
|
||||
tex: Default::default(),
|
||||
|
|
@ -172,7 +172,7 @@ impl GuiElement for Button {
|
|||
None,
|
||||
&self.font,
|
||||
&text,
|
||||
Color::from_gray(0),
|
||||
Color::from_gray_srgb(0),
|
||||
Some(scale as _),
|
||||
true,
|
||||
);
|
||||
|
|
@ -296,7 +296,7 @@ impl GuiElement for Label {
|
|||
None,
|
||||
&self.font,
|
||||
&text,
|
||||
Color::from_gray(255),
|
||||
Color::from_gray_srgb(255),
|
||||
Some(scale as _),
|
||||
false,
|
||||
);
|
||||
|
|
@ -635,7 +635,7 @@ impl WindowData {
|
|||
AcquireSync::Implicit,
|
||||
ReleaseSync::Implicit,
|
||||
self.scale.get(),
|
||||
Some(&Color::from_gray(0)),
|
||||
Some(&Color::from_gray_srgb(0)),
|
||||
None,
|
||||
&mut |r| {
|
||||
if let Some(content) = self.content.get() {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use {
|
|||
renderer::renderer_base::RendererBase,
|
||||
scale::Scale,
|
||||
state::State,
|
||||
theme::Color,
|
||||
theme::{Color, TransferFunction},
|
||||
tree::{
|
||||
ContainerNode, DisplayNode, FloatNode, OutputNode, PlaceholderNode, ToplevelData,
|
||||
ToplevelNodeBase, WorkspaceNode,
|
||||
|
|
@ -202,7 +202,7 @@ impl Renderer<'_> {
|
|||
let pos = placeholder.tl_data().pos.get();
|
||||
self.base.fill_boxes(
|
||||
std::slice::from_ref(&pos.at_point(x, y)),
|
||||
&Color::from_rgba_straight(20, 20, 20, 255),
|
||||
&Color::from_srgba_straight(20, 20, 20, 255),
|
||||
);
|
||||
if let Some(tex) = placeholder.textures.borrow().get(&self.base.scale) {
|
||||
if let Some(texture) = tex.texture() {
|
||||
|
|
@ -448,8 +448,15 @@ impl Renderer<'_> {
|
|||
Some(bounds) => rect.intersect(*bounds),
|
||||
};
|
||||
if !rect.is_empty() {
|
||||
let color = Color::from_u32_premultiplied(
|
||||
TransferFunction::Srgb,
|
||||
color[0],
|
||||
color[1],
|
||||
color[2],
|
||||
color[3],
|
||||
);
|
||||
self.base.ops.push(GfxApiOpt::Sync);
|
||||
self.base.fill_scaled_boxes(&[rect], color, alpha);
|
||||
self.base.fill_scaled_boxes(&[rect], &color, alpha);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ use {
|
|||
},
|
||||
},
|
||||
rect::{Rect, Region},
|
||||
theme::Color,
|
||||
theme::{Color, TransferFunction},
|
||||
utils::{
|
||||
clonecell::CloneCell, double_buffered::DoubleBuffered, on_drop_event::OnDropEvent,
|
||||
},
|
||||
|
|
@ -187,9 +187,9 @@ fn render(
|
|||
data.layout.set_text(text);
|
||||
}
|
||||
let font_height = data.layout.pixel_size().1;
|
||||
let [r, g, b, a] = color.to_array(TransferFunction::Srgb);
|
||||
data.cctx.set_operator(CAIRO_OPERATOR_SOURCE);
|
||||
data.cctx
|
||||
.set_source_rgba(color.r as _, color.g as _, color.b as _, color.a as _);
|
||||
data.cctx.set_source_rgba(r as _, g as _, b as _, a as _);
|
||||
let y = y.unwrap_or((height - font_height) / 2);
|
||||
data.cctx.move_to(x as f64, y as f64);
|
||||
data.layout.show_layout();
|
||||
|
|
|
|||
192
src/theme.rs
192
src/theme.rs
|
|
@ -3,12 +3,18 @@ use {
|
|||
std::{cell::Cell, cmp::Ordering, ops::Mul, sync::Arc},
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum TransferFunction {
|
||||
Srgb,
|
||||
Linear,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub struct Color {
|
||||
pub r: f32,
|
||||
pub g: f32,
|
||||
pub b: f32,
|
||||
pub a: f32,
|
||||
r: f32,
|
||||
g: f32,
|
||||
b: f32,
|
||||
a: f32,
|
||||
}
|
||||
|
||||
impl Eq for Color {}
|
||||
|
|
@ -65,77 +71,155 @@ impl Color {
|
|||
a: 1.0,
|
||||
};
|
||||
|
||||
pub fn from_gray(g: u8) -> Self {
|
||||
Self::from_rgb(g, g, g)
|
||||
pub fn new(transfer_function: TransferFunction, mut r: f32, mut g: f32, mut b: f32) -> Self {
|
||||
fn srgb(c: f32) -> f32 {
|
||||
if c <= 0.04045 {
|
||||
c / 12.92
|
||||
} else {
|
||||
((c + 0.055) / 1.055).powf(2.4)
|
||||
}
|
||||
}
|
||||
#[inline(always)]
|
||||
fn linear(c: f32) -> f32 {
|
||||
c
|
||||
}
|
||||
macro_rules! convert {
|
||||
($tf:ident) => {{
|
||||
r = $tf(r);
|
||||
g = $tf(g);
|
||||
b = $tf(b);
|
||||
}};
|
||||
}
|
||||
match transfer_function {
|
||||
TransferFunction::Srgb => convert!(srgb),
|
||||
TransferFunction::Linear => convert!(linear),
|
||||
}
|
||||
Self { r, g, b, a: 1.0 }
|
||||
}
|
||||
|
||||
pub fn from_rgb(r: u8, g: u8, b: u8) -> Self {
|
||||
Self {
|
||||
r: to_f32(r),
|
||||
g: to_f32(g),
|
||||
b: to_f32(b),
|
||||
a: 1.0,
|
||||
pub fn new_premultiplied(
|
||||
transfer_function: TransferFunction,
|
||||
mut r: f32,
|
||||
mut g: f32,
|
||||
mut b: f32,
|
||||
a: f32,
|
||||
) -> Self {
|
||||
if transfer_function == TransferFunction::Linear {
|
||||
return Self { r, g, b, a };
|
||||
}
|
||||
if a < 1.0 && a > 0.0 {
|
||||
for c in [&mut r, &mut g, &mut b] {
|
||||
*c /= a;
|
||||
}
|
||||
}
|
||||
let mut c = Self::new(transfer_function, r, g, b);
|
||||
if a < 1.0 {
|
||||
c = c * a;
|
||||
}
|
||||
c
|
||||
}
|
||||
|
||||
pub fn is_opaque(&self) -> bool {
|
||||
self.a >= 1.0
|
||||
}
|
||||
|
||||
pub fn from_gray_srgb(g: u8) -> Self {
|
||||
Self::from_srgb(g, g, g)
|
||||
}
|
||||
|
||||
pub fn from_srgb(r: u8, g: u8, b: u8) -> Self {
|
||||
Self::new(TransferFunction::Srgb, to_f32(r), to_f32(g), to_f32(b))
|
||||
}
|
||||
|
||||
#[cfg_attr(not(feature = "it"), expect(dead_code))]
|
||||
pub fn from_rgba_premultiplied(r: u8, g: u8, b: u8, a: u8) -> Self {
|
||||
Self {
|
||||
r: to_f32(r),
|
||||
g: to_f32(g),
|
||||
b: to_f32(b),
|
||||
a: to_f32(a),
|
||||
}
|
||||
pub fn from_srgba_premultiplied(r: u8, g: u8, b: u8, a: u8) -> Self {
|
||||
Self::new_premultiplied(
|
||||
TransferFunction::Srgb,
|
||||
to_f32(r),
|
||||
to_f32(g),
|
||||
to_f32(b),
|
||||
to_f32(a),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn from_u32_rgba_premultiplied(r: u32, g: u32, b: u32, a: u32) -> Self {
|
||||
pub fn from_u32_premultiplied(
|
||||
transfer_function: TransferFunction,
|
||||
r: u32,
|
||||
g: u32,
|
||||
b: u32,
|
||||
a: u32,
|
||||
) -> Self {
|
||||
fn to_f32(c: u32) -> f32 {
|
||||
((c as f64) / (u32::MAX as f64)) as f32
|
||||
}
|
||||
Self {
|
||||
r: to_f32(r),
|
||||
g: to_f32(g),
|
||||
b: to_f32(b),
|
||||
a: to_f32(a),
|
||||
}
|
||||
Self::new_premultiplied(
|
||||
transfer_function,
|
||||
to_f32(r),
|
||||
to_f32(g),
|
||||
to_f32(b),
|
||||
to_f32(a),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn from_rgba_straight(r: u8, g: u8, b: u8, a: u8) -> Self {
|
||||
let alpha = to_f32(a);
|
||||
Self {
|
||||
r: to_f32(r) * alpha,
|
||||
g: to_f32(g) * alpha,
|
||||
b: to_f32(b) * alpha,
|
||||
a: alpha,
|
||||
pub fn from_srgba_straight(r: u8, g: u8, b: u8, a: u8) -> Self {
|
||||
let mut c = Self::new(TransferFunction::Srgb, to_f32(r), to_f32(g), to_f32(b));
|
||||
if a < 255 {
|
||||
c = c * to_f32(a);
|
||||
}
|
||||
c
|
||||
}
|
||||
|
||||
#[cfg_attr(not(feature = "it"), expect(dead_code))]
|
||||
pub fn to_rgba_premultiplied(self) -> [u8; 4] {
|
||||
[to_u8(self.r), to_u8(self.g), to_u8(self.b), to_u8(self.a)]
|
||||
pub fn to_srgba_premultiplied(self) -> [u8; 4] {
|
||||
let [r, g, b, a] = self.to_array(TransferFunction::Srgb);
|
||||
[to_u8(r), to_u8(g), to_u8(b), to_u8(a)]
|
||||
}
|
||||
|
||||
pub fn to_array_srgb(self, alpha: Option<f32>) -> [f32; 4] {
|
||||
let a = alpha.unwrap_or(1.0);
|
||||
[self.r * a, self.g * a, self.b * a, self.a * a]
|
||||
pub fn to_array(self, transfer_function: TransferFunction) -> [f32; 4] {
|
||||
self.to_array2(transfer_function, None)
|
||||
}
|
||||
|
||||
pub fn to_array_linear(self, alpha: Option<f32>) -> [f32; 4] {
|
||||
fn to_linear(srgb: f32) -> f32 {
|
||||
if srgb <= 0.04045 {
|
||||
srgb / 12.92
|
||||
pub fn to_array2(self, transfer_function: TransferFunction, alpha: Option<f32>) -> [f32; 4] {
|
||||
let mut res = [self.r, self.g, self.b, self.a];
|
||||
fn srgb(c: f32) -> f32 {
|
||||
if c <= 0.0031308 {
|
||||
c * 12.92
|
||||
} else {
|
||||
((srgb + 0.055) / 1.055).powf(2.4)
|
||||
1.055 * c.powf(1.0 / 2.4) - 0.055
|
||||
}
|
||||
}
|
||||
let a1 = if self.a == 0.0 { 1.0 } else { self.a };
|
||||
let a2 = self.a * alpha.unwrap_or(1.0);
|
||||
[
|
||||
to_linear(self.r / a1) * a2,
|
||||
to_linear(self.g / a1) * a2,
|
||||
to_linear(self.b / a1) * a2,
|
||||
a2,
|
||||
]
|
||||
fn linear(c: f32) -> f32 {
|
||||
c
|
||||
}
|
||||
macro_rules! convert {
|
||||
($tf:ident) => {{
|
||||
for c in &mut res[..3] {
|
||||
*c = $tf(*c);
|
||||
}
|
||||
}};
|
||||
}
|
||||
if transfer_function != TransferFunction::Linear {
|
||||
if self.a < 1.0 && self.a > 0.0 {
|
||||
for c in &mut res[..3] {
|
||||
*c /= self.a;
|
||||
}
|
||||
}
|
||||
match transfer_function {
|
||||
TransferFunction::Srgb => convert!(srgb),
|
||||
TransferFunction::Linear => convert!(linear),
|
||||
}
|
||||
if self.a < 1.0 {
|
||||
for c in &mut res[..3] {
|
||||
*c *= self.a;
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(a) = alpha {
|
||||
for c in &mut res {
|
||||
*c *= a;
|
||||
}
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
#[cfg_attr(not(feature = "it"), expect(dead_code))]
|
||||
|
|
@ -152,7 +236,7 @@ impl Color {
|
|||
impl From<jay_config::theme::Color> for Color {
|
||||
fn from(f: jay_config::theme::Color) -> Self {
|
||||
let [r, g, b, a] = f.to_f32_premultiplied();
|
||||
Self { r, g, b, a }
|
||||
Self::new_premultiplied(TransferFunction::Srgb, r, g, b, a)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -184,10 +268,10 @@ macro_rules! colors {
|
|||
}
|
||||
};
|
||||
(@colors ($r:expr, $g:expr, $b:expr)) => {
|
||||
Color::from_rgb($r, $g, $b)
|
||||
Color::from_srgb($r, $g, $b)
|
||||
};
|
||||
(@colors ($r:expr, $g:expr, $b:expr, $a:expr)) => {
|
||||
Color::from_rgba_straight($r, $g, $b, $a)
|
||||
Color::from_srgba_straight($r, $g, $b, $a)
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue