/*
* This file is part of libplacebo.
*
* libplacebo is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* libplacebo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with libplacebo. If not, see .
*/
#include "gpu.h"
#include "formats.h"
static inline UINT tex_subresource(pl_tex tex)
{
struct pl_tex_d3d11 *tex_p = PL_PRIV(tex);
return tex_p->array_slice >= 0 ? tex_p->array_slice : 0;
}
static bool tex_init(pl_gpu gpu, pl_tex tex)
{
struct pl_gpu_d3d11 *p = PL_PRIV(gpu);
struct d3d11_ctx *ctx = p->ctx;
struct pl_tex_d3d11 *tex_p = PL_PRIV(tex);
// View formats may be omitted when they match the texture format, but for
// simplicity's sake we always set it. It will match the texture format for
// textures created with tex_create, but it can be different for video
// textures wrapped with pl_d3d11_wrap.
DXGI_FORMAT fmt = fmt_to_dxgi(tex->params.format);
if (tex->params.sampleable || tex->params.storable) {
D3D11_SHADER_RESOURCE_VIEW_DESC srvdesc = {
.Format = fmt,
};
switch (pl_tex_params_dimension(tex->params)) {
case 1:
if (tex_p->array_slice >= 0) {
srvdesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE1DARRAY;
srvdesc.Texture1DArray.MipLevels = 1;
srvdesc.Texture1DArray.FirstArraySlice = tex_p->array_slice;
srvdesc.Texture1DArray.ArraySize = 1;
} else {
srvdesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE1D;
srvdesc.Texture1D.MipLevels = 1;
}
break;
case 2:
if (tex_p->array_slice >= 0) {
srvdesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2DARRAY;
srvdesc.Texture2DArray.MipLevels = 1;
srvdesc.Texture2DArray.FirstArraySlice = tex_p->array_slice;
srvdesc.Texture2DArray.ArraySize = 1;
} else {
srvdesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
srvdesc.Texture2D.MipLevels = 1;
}
break;
case 3:
// D3D11 does not have Texture3D arrays
srvdesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE3D;
srvdesc.Texture3D.MipLevels = 1;
break;
}
D3D(ID3D11Device_CreateShaderResourceView(p->dev, tex_p->res, &srvdesc,
&tex_p->srv));
}
if (tex->params.renderable) {
D3D11_RENDER_TARGET_VIEW_DESC rtvdesc = {
.Format = fmt,
};
switch (pl_tex_params_dimension(tex->params)) {
case 1:
if (tex_p->array_slice >= 0) {
rtvdesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE1DARRAY;
rtvdesc.Texture1DArray.FirstArraySlice = tex_p->array_slice;
rtvdesc.Texture1DArray.ArraySize = 1;
} else {
rtvdesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE1D;
}
break;
case 2:
if (tex_p->array_slice >= 0) {
rtvdesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2DARRAY;
rtvdesc.Texture2DArray.FirstArraySlice = tex_p->array_slice;
rtvdesc.Texture2DArray.ArraySize = 1;
} else {
rtvdesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
}
break;
case 3:
// D3D11 does not have Texture3D arrays
rtvdesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE3D;
rtvdesc.Texture3D.WSize = -1;
break;
}
D3D(ID3D11Device_CreateRenderTargetView(p->dev, tex_p->res, &rtvdesc,
&tex_p->rtv));
}
if (p->fl >= D3D_FEATURE_LEVEL_11_0 && tex->params.storable) {
D3D11_UNORDERED_ACCESS_VIEW_DESC uavdesc = {
.Format = fmt,
};
switch (pl_tex_params_dimension(tex->params)) {
case 1:
if (tex_p->array_slice >= 0) {
uavdesc.ViewDimension = D3D11_UAV_DIMENSION_TEXTURE1DARRAY;
uavdesc.Texture1DArray.FirstArraySlice = tex_p->array_slice;
uavdesc.Texture1DArray.ArraySize = 1;
} else {
uavdesc.ViewDimension = D3D11_UAV_DIMENSION_TEXTURE1D;
}
break;
case 2:
if (tex_p->array_slice >= 0) {
uavdesc.ViewDimension = D3D11_UAV_DIMENSION_TEXTURE2DARRAY;
uavdesc.Texture2DArray.FirstArraySlice = tex_p->array_slice;
uavdesc.Texture2DArray.ArraySize = 1;
} else {
uavdesc.ViewDimension = D3D11_UAV_DIMENSION_TEXTURE2D;
}
break;
case 3:
// D3D11 does not have Texture3D arrays
uavdesc.ViewDimension = D3D11_UAV_DIMENSION_TEXTURE3D;
uavdesc.Texture3D.WSize = -1;
break;
}
D3D(ID3D11Device_CreateUnorderedAccessView(p->dev, tex_p->res, &uavdesc,
&tex_p->uav));
}
return true;
error:
return false;
}
void pl_d3d11_tex_destroy(pl_gpu gpu, pl_tex tex)
{
struct pl_gpu_d3d11 *p = PL_PRIV(gpu);
struct d3d11_ctx *ctx = p->ctx;
struct pl_tex_d3d11 *tex_p = PL_PRIV(tex);
SAFE_RELEASE(tex_p->srv);
SAFE_RELEASE(tex_p->rtv);
SAFE_RELEASE(tex_p->uav);
SAFE_RELEASE(tex_p->res);
SAFE_RELEASE(tex_p->staging);
pl_d3d11_flush_message_queue(ctx, "After texture destroy");
pl_free((void *) tex);
}
pl_tex pl_d3d11_tex_create(pl_gpu gpu, const struct pl_tex_params *params)
{
struct pl_gpu_d3d11 *p = PL_PRIV(gpu);
struct d3d11_ctx *ctx = p->ctx;
struct pl_tex_t *tex = pl_zalloc_obj(NULL, tex, struct pl_tex_d3d11);
tex->params = *params;
tex->params.initial_data = NULL;
tex->sampler_type = PL_SAMPLER_NORMAL;
struct pl_tex_d3d11 *tex_p = PL_PRIV(tex);
DXGI_FORMAT dxfmt = fmt_to_dxgi(params->format);
D3D11_USAGE usage = D3D11_USAGE_DEFAULT;
D3D11_BIND_FLAG bind_flags = 0;
if (params->format->emulated) {
tex_p->texel_fmt = pl_find_fmt(gpu, params->format->type, 1, 0,
params->format->host_bits[0],
PL_FMT_CAP_TEXEL_UNIFORM);
if (!tex_p->texel_fmt) {
PL_ERR(gpu, "Failed picking texel format for emulated texture!");
goto error;
}
tex->params.storable = true;
}
if (p->fl >= D3D_FEATURE_LEVEL_11_0) {
// On >=FL11_0, blit emulation needs image storage
tex->params.storable |= params->blit_src || params->blit_dst;
// Blit emulation can use a sampler for linear filtering during stretch
if ((tex->params.format->caps & PL_FMT_CAP_LINEAR) && params->blit_src)
tex->params.sampleable = true;
} else {
// On params.sampleable |= params->blit_src;
tex->params.renderable |= params->blit_dst;
}
if (tex->params.sampleable)
bind_flags |= D3D11_BIND_SHADER_RESOURCE;
if (tex->params.renderable)
bind_flags |= D3D11_BIND_RENDER_TARGET;
if (p->fl >= D3D_FEATURE_LEVEL_11_0 && tex->params.storable)
bind_flags |= D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS;
// Apparently IMMUTABLE textures are efficient, so try to infer whether we
// can use one
if (params->initial_data && !params->format->emulated &&
!tex->params.renderable && !tex->params.storable && !params->host_writable)
{
usage = D3D11_USAGE_IMMUTABLE;
}
// In FL9_x, resources with only D3D11_BIND_SHADER_RESOURCE can't be copied
// from GPU-accessible memory to CPU-accessible memory. The only other bind
// flag we set on this FL is D3D11_BIND_RENDER_TARGET, so set it.
if (p->fl <= D3D_FEATURE_LEVEL_9_3 && tex->params.host_readable)
bind_flags |= D3D11_BIND_RENDER_TARGET;
// In FL9_x, when using DEFAULT or IMMUTABLE, BindFlags cannot be zero
if (p->fl <= D3D_FEATURE_LEVEL_9_3 && !bind_flags)
bind_flags |= D3D11_BIND_SHADER_RESOURCE;
D3D11_SUBRESOURCE_DATA data;
D3D11_SUBRESOURCE_DATA *pdata = NULL;
if (params->initial_data && !params->format->emulated) {
data = (D3D11_SUBRESOURCE_DATA) {
.pSysMem = params->initial_data,
.SysMemPitch = params->w * params->format->texel_size,
};
if (params->d)
data.SysMemSlicePitch = data.SysMemPitch * params->h;
pdata = &data;
}
switch (pl_tex_params_dimension(*params)) {
case 1:;
D3D11_TEXTURE1D_DESC desc1d = {
.Width = params->w,
.MipLevels = 1,
.ArraySize = 1,
.Format = dxfmt,
.Usage = usage,
.BindFlags = bind_flags,
};
D3D(ID3D11Device_CreateTexture1D(p->dev, &desc1d, pdata, &tex_p->tex1d));
tex_p->res = (ID3D11Resource *)tex_p->tex1d;
// Create a staging texture with CPU access for pl_tex_download()
if (params->host_readable) {
desc1d.BindFlags = 0;
desc1d.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
desc1d.Usage = D3D11_USAGE_STAGING;
D3D(ID3D11Device_CreateTexture1D(p->dev, &desc1d, NULL,
&tex_p->staging1d));
tex_p->staging = (ID3D11Resource *) tex_p->staging1d;
}
break;
case 2:;
D3D11_TEXTURE2D_DESC desc2d = {
.Width = params->w,
.Height = params->h,
.MipLevels = 1,
.ArraySize = 1,
.SampleDesc.Count = 1,
.Format = dxfmt,
.Usage = usage,
.BindFlags = bind_flags,
};
D3D(ID3D11Device_CreateTexture2D(p->dev, &desc2d, pdata, &tex_p->tex2d));
tex_p->res = (ID3D11Resource *)tex_p->tex2d;
// Create a staging texture with CPU access for pl_tex_download()
if (params->host_readable) {
desc2d.BindFlags = 0;
desc2d.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
desc2d.Usage = D3D11_USAGE_STAGING;
D3D(ID3D11Device_CreateTexture2D(p->dev, &desc2d, NULL,
&tex_p->staging2d));
tex_p->staging = (ID3D11Resource *) tex_p->staging2d;
}
break;
case 3:;
D3D11_TEXTURE3D_DESC desc3d = {
.Width = params->w,
.Height = params->h,
.Depth = params->d,
.MipLevels = 1,
.Format = dxfmt,
.Usage = usage,
.BindFlags = bind_flags,
};
D3D(ID3D11Device_CreateTexture3D(p->dev, &desc3d, pdata, &tex_p->tex3d));
tex_p->res = (ID3D11Resource *)tex_p->tex3d;
// Create a staging texture with CPU access for pl_tex_download()
if (params->host_readable) {
desc3d.BindFlags = 0;
desc3d.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
desc3d.Usage = D3D11_USAGE_STAGING;
D3D(ID3D11Device_CreateTexture3D(p->dev, &desc3d, NULL,
&tex_p->staging3d));
tex_p->staging = (ID3D11Resource *) tex_p->staging3d;
}
break;
default:
pl_unreachable();
}
tex_p->array_slice = -1;
if (!tex_init(gpu, tex))
goto error;
if (params->initial_data && params->format->emulated) {
struct pl_tex_transfer_params ul_params = {
.tex = tex,
.ptr = (void *) params->initial_data,
.rc = { 0, 0, 0, params->w, params->h, params->d },
};
// Since we re-use GPU helpers which require writable images, just fake it
bool writable = tex->params.host_writable;
tex->params.host_writable = true;
if (!pl_tex_upload(gpu, &ul_params))
goto error;
tex->params.host_writable = writable;
}
pl_d3d11_flush_message_queue(ctx, "After texture create");
return tex;
error:
pl_d3d11_tex_destroy(gpu, tex);
return NULL;
}
pl_tex pl_d3d11_wrap(pl_gpu gpu, const struct pl_d3d11_wrap_params *params)
{
struct pl_gpu_d3d11 *p = PL_PRIV(gpu);
struct d3d11_ctx *ctx = p->ctx;
struct pl_tex_t *tex = pl_zalloc_obj(NULL, tex, struct pl_tex_d3d11);
tex->sampler_type = PL_SAMPLER_NORMAL;
struct pl_tex_d3d11 *tex_p = PL_PRIV(tex);
DXGI_FORMAT fmt = DXGI_FORMAT_UNKNOWN;
D3D11_USAGE usage = D3D11_USAGE_DEFAULT;
D3D11_BIND_FLAG bind_flags = 0;
UINT mip_levels = 1;
UINT array_size = 1;
UINT sample_count = 1;
D3D11_RESOURCE_DIMENSION type;
ID3D11Resource_GetType(params->tex, &type);
switch (type) {
case D3D11_RESOURCE_DIMENSION_TEXTURE1D:
D3D(ID3D11Resource_QueryInterface(params->tex, &IID_ID3D11Texture1D,
(void **) &tex_p->tex1d));
tex_p->res = (ID3D11Resource *) tex_p->tex1d;
D3D11_TEXTURE1D_DESC desc1d;
ID3D11Texture1D_GetDesc(tex_p->tex1d, &desc1d);
tex->params.w = desc1d.Width;
mip_levels = desc1d.MipLevels;
array_size = desc1d.ArraySize;
fmt = desc1d.Format;
usage = desc1d.Usage;
bind_flags = desc1d.BindFlags;
break;
case D3D11_RESOURCE_DIMENSION_TEXTURE2D:
D3D(ID3D11Resource_QueryInterface(params->tex, &IID_ID3D11Texture2D,
(void **) &tex_p->tex2d));
tex_p->res = (ID3D11Resource *) tex_p->tex2d;
D3D11_TEXTURE2D_DESC desc2d;
ID3D11Texture2D_GetDesc(tex_p->tex2d, &desc2d);
tex->params.w = desc2d.Width;
tex->params.h = desc2d.Height;
mip_levels = desc2d.MipLevels;
array_size = desc2d.ArraySize;
fmt = desc2d.Format;
sample_count = desc2d.SampleDesc.Count;
usage = desc2d.Usage;
bind_flags = desc2d.BindFlags;
// Allow the format and size of 2D textures to be overridden to support
// shader views of video resources
if (params->fmt) {
fmt = params->fmt;
tex->params.w = params->w;
tex->params.h = params->h;
}
break;
case D3D11_RESOURCE_DIMENSION_TEXTURE3D:
D3D(ID3D11Resource_QueryInterface(params->tex, &IID_ID3D11Texture3D,
(void **) &tex_p->tex3d));
tex_p->res = (ID3D11Resource *) tex_p->tex3d;
D3D11_TEXTURE3D_DESC desc3d;
ID3D11Texture3D_GetDesc(tex_p->tex3d, &desc3d);
tex->params.w = desc3d.Width;
tex->params.h = desc3d.Height;
tex->params.d = desc3d.Depth;
mip_levels = desc3d.MipLevels;
fmt = desc3d.Format;
usage = desc3d.Usage;
bind_flags = desc3d.BindFlags;
break;
case D3D11_RESOURCE_DIMENSION_UNKNOWN:
case D3D11_RESOURCE_DIMENSION_BUFFER:
PL_ERR(gpu, "Resource is not suitable to wrap");
goto error;
}
if (mip_levels != 1) {
PL_ERR(gpu, "Mipmapped textures not supported for wrapping");
goto error;
}
if (sample_count != 1) {
PL_ERR(gpu, "Multisampled textures not supported for wrapping");
goto error;
}
if (usage != D3D11_USAGE_DEFAULT) {
PL_ERR(gpu, "Resource is not D3D11_USAGE_DEFAULT");
goto error;
}
if (array_size > 1) {
if (params->array_slice < 0 || params->array_slice >= array_size) {
PL_ERR(gpu, "array_slice out of range");
goto error;
}
tex_p->array_slice = params->array_slice;
} else {
tex_p->array_slice = -1;
}
if (bind_flags & D3D11_BIND_SHADER_RESOURCE) {
tex->params.sampleable = true;
// Blit emulation uses a render pass on fl < D3D_FEATURE_LEVEL_11_0)
tex->params.blit_src = true;
}
if (bind_flags & D3D11_BIND_RENDER_TARGET) {
tex->params.renderable = true;
// Blit emulation uses a render pass on fl < D3D_FEATURE_LEVEL_11_0)
tex->params.blit_dst = true;
}
static const D3D11_BIND_FLAG storable_flags =
D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_SHADER_RESOURCE;
if ((bind_flags & storable_flags) == storable_flags) {
tex->params.storable = true;
// Blit emulation uses image storage on >=FL11_0. A feature level check
// isn't required because params.blit_src = tex->params.blit_dst = true;
}
for (int i = 0; i < gpu->num_formats; i++) {
DXGI_FORMAT target_fmt = fmt_to_dxgi(gpu->formats[i]);
if (fmt == target_fmt) {
tex->params.format = gpu->formats[i];
break;
}
}
if (!tex->params.format) {
PL_ERR(gpu, "Could not find a suitable pl_fmt for wrapped resource");
goto error;
}
if (!tex_init(gpu, tex))
goto error;
pl_d3d11_flush_message_queue(ctx, "After texture wrap");
return tex;
error:
pl_d3d11_tex_destroy(gpu, tex);
return NULL;
}
void pl_d3d11_tex_invalidate(pl_gpu gpu, pl_tex tex)
{
struct pl_gpu_d3d11 *p = PL_PRIV(gpu);
struct d3d11_ctx *ctx = p->ctx;
struct pl_tex_d3d11 *tex_p = PL_PRIV(tex);
// Resource discarding requires D3D11.1
if (!p->imm1)
return;
// Prefer discarding a view to discarding the whole resource. The reason
// for this is that a pl_tex can refer to a single member of a texture
// array. Discarding the SRV, RTV or UAV should only discard that member.
if (tex_p->rtv) {
ID3D11DeviceContext1_DiscardView(p->imm1, (ID3D11View *) tex_p->rtv);
} else if (tex_p->uav) {
ID3D11DeviceContext1_DiscardView(p->imm1, (ID3D11View *) tex_p->uav);
} else if (tex_p->srv) {
ID3D11DeviceContext1_DiscardView(p->imm1, (ID3D11View *) tex_p->srv);
} else if (tex_p->array_slice < 0) {
// If there are no views, only discard if the ID3D11Resource is not a
// texture array
ID3D11DeviceContext1_DiscardResource(p->imm1, tex_p->res);
}
pl_d3d11_flush_message_queue(ctx, "After texture invalidate");
}
void pl_d3d11_tex_clear_ex(pl_gpu gpu, pl_tex tex,
const union pl_clear_color color)
{
struct pl_gpu_d3d11 *p = PL_PRIV(gpu);
struct d3d11_ctx *ctx = p->ctx;
struct pl_tex_d3d11 *tex_p = PL_PRIV(tex);
if (tex->params.format->type == PL_FMT_UINT) {
if (tex_p->uav) {
ID3D11DeviceContext_ClearUnorderedAccessViewUint(p->imm, tex_p->uav,
color.u);
} else {
float c[4] = { color.u[0], color.u[1], color.u[2], color.u[3] };
ID3D11DeviceContext_ClearRenderTargetView(p->imm, tex_p->rtv, c);
}
} else if (tex->params.format->type == PL_FMT_SINT) {
if (tex_p->uav) {
ID3D11DeviceContext_ClearUnorderedAccessViewUint(p->imm, tex_p->uav,
(const uint32_t *)color.i);
} else {
float c[4] = { color.i[0], color.i[1], color.i[2], color.i[3] };
ID3D11DeviceContext_ClearRenderTargetView(p->imm, tex_p->rtv, c);
}
} else if (tex_p->rtv) {
ID3D11DeviceContext_ClearRenderTargetView(p->imm, tex_p->rtv, color.f);
} else {
ID3D11DeviceContext_ClearUnorderedAccessViewFloat(p->imm, tex_p->uav, color.f);
}
pl_d3d11_flush_message_queue(ctx, "After texture clear");
}
#define pl_rect3d_to_box(rc) \
((D3D11_BOX) { \
.left = rc.x0, .top = rc.y0, .front = rc.z0, \
.right = rc.x1, .bottom = rc.y1, .back = rc.z1, \
})
void pl_d3d11_tex_blit(pl_gpu gpu, const struct pl_tex_blit_params *params)
{
struct pl_gpu_d3d11 *p = PL_PRIV(gpu);
struct d3d11_ctx *ctx = p->ctx;
struct pl_tex_d3d11 *src_p = PL_PRIV(params->src);
DXGI_FORMAT src_fmt = fmt_to_dxgi(params->src->params.format);
struct pl_tex_d3d11 *dst_p = PL_PRIV(params->dst);
DXGI_FORMAT dst_fmt = fmt_to_dxgi(params->dst->params.format);
// If the blit operation doesn't require flipping, scaling or format
// conversion, we can use CopySubresourceRegion
pl_rect3d src_rc = params->src_rc, dst_rc = params->dst_rc;
if (pl_rect3d_eq(src_rc, dst_rc) && src_fmt == dst_fmt) {
pl_rect3d rc = params->src_rc;
pl_rect3d_normalize(&rc);
ID3D11DeviceContext_CopySubresourceRegion(p->imm, dst_p->res,
tex_subresource(params->dst), rc.x0, rc.y0, rc.z0, src_p->res,
tex_subresource(params->src), &pl_rect3d_to_box(rc));
} else if (p->fl >= D3D_FEATURE_LEVEL_11_0) {
if (!pl_tex_blit_compute(gpu, params))
PL_ERR(gpu, "Failed compute shader fallback blit");
} else {
pl_tex_blit_raster(gpu, params);
}
pl_d3d11_flush_message_queue(ctx, "After texture blit");
}
bool pl_d3d11_tex_upload(pl_gpu gpu, const struct pl_tex_transfer_params *params)
{
struct pl_gpu_d3d11 *p = PL_PRIV(gpu);
struct d3d11_ctx *ctx = p->ctx;
pl_tex tex = params->tex;
pl_fmt fmt = tex->params.format;
struct pl_tex_d3d11 *tex_p = PL_PRIV(tex);
struct pl_tex_transfer_params *slices = NULL;
bool ret = false;
pl_d3d11_timer_start(gpu, params->timer);
if (fmt->emulated) {
int num_slices = pl_tex_transfer_slices(gpu, tex_p->texel_fmt, params, &slices);
for (int i = 0; i < num_slices; i++) {
// Copy the source data buffer into an intermediate buffer
pl_buf tbuf = pl_buf_create(gpu, pl_buf_params(
.memory_type = PL_BUF_MEM_DEVICE,
.format = tex_p->texel_fmt,
.size = pl_tex_transfer_size(&slices[i]),
.initial_data = slices[i].ptr,
.storable = true,
));
if (!tbuf) {
PL_ERR(gpu, "Failed creating buffer for tex upload fallback!");
goto error;
}
slices[i].ptr = NULL;
slices[i].buf = tbuf;
slices[i].buf_offset = 0;
bool ok = pl_tex_upload_texel(gpu, &slices[i]);
pl_buf_destroy(gpu, &tbuf);
if (!ok)
goto error;
}
} else {
ID3D11DeviceContext_UpdateSubresource(p->imm, tex_p->res,
tex_subresource(tex), &pl_rect3d_to_box(params->rc), params->ptr,
params->row_pitch, params->depth_pitch);
}
ret = true;
error:
pl_d3d11_timer_end(gpu, params->timer);
pl_d3d11_flush_message_queue(ctx, "After texture upload");
pl_free(slices);
return ret;
}
bool pl_d3d11_tex_download(pl_gpu gpu, const struct pl_tex_transfer_params *params)
{
struct pl_gpu_d3d11 *p = PL_PRIV(gpu);
struct d3d11_ctx *ctx = p->ctx;
const struct pl_tex_t *tex = params->tex;
pl_fmt fmt = tex->params.format;
struct pl_tex_d3d11 *tex_p = PL_PRIV(tex);
struct pl_tex_transfer_params *slices = NULL;
bool ret = false;
if (!tex_p->staging)
return false;
pl_d3d11_timer_start(gpu, params->timer);
if (fmt->emulated) {
pl_buf tbuf = NULL;
int num_slices = pl_tex_transfer_slices(gpu, tex_p->texel_fmt, params, &slices);
for (int i = 0; i < num_slices; i++) {
const size_t slice_size = pl_tex_transfer_size(&slices[i]);
bool ok = pl_buf_recreate(gpu, &tbuf, pl_buf_params(
.storable = true,
.size = slice_size,
.memory_type = PL_BUF_MEM_DEVICE,
.format = tex_p->texel_fmt,
.host_readable = true,
));
if (!ok) {
PL_ERR(gpu, "Failed creating buffer for tex download fallback!");
goto error;
}
void *ptr = slices[i].ptr;
slices[i].ptr = NULL;
slices[i].buf = tbuf;
slices[i].buf_offset = 0;
// Download into an intermediate buffer first
ok = pl_tex_download_texel(gpu, &slices[i]);
ok = ok && pl_buf_read(gpu, tbuf, 0, ptr, slice_size);
if (!ok) {
pl_buf_destroy(gpu, &tbuf);
goto error;
}
}
pl_buf_destroy(gpu, &tbuf);
} else {
ID3D11DeviceContext_CopySubresourceRegion(p->imm,
(ID3D11Resource *) tex_p->staging, 0, params->rc.x0, params->rc.y0,
params->rc.z0, tex_p->res, tex_subresource(tex),
&pl_rect3d_to_box(params->rc));
D3D11_MAPPED_SUBRESOURCE lock;
D3D(ID3D11DeviceContext_Map(p->imm, (ID3D11Resource *) tex_p->staging, 0,
D3D11_MAP_READ, 0, &lock));
char *cdst = params->ptr;
char *csrc = lock.pData;
size_t line_size = pl_rect_w(params->rc) * tex->params.format->texel_size;
for (int z = 0; z < pl_rect_d(params->rc); z++) {
for (int y = 0; y < pl_rect_h(params->rc); y++) {
memcpy(cdst + z * params->depth_pitch + y * params->row_pitch,
csrc + (params->rc.z0 + z) * lock.DepthPitch +
(params->rc.y0 + y) * lock.RowPitch + params->rc.x0,
line_size);
}
}
ID3D11DeviceContext_Unmap(p->imm, (ID3D11Resource*)tex_p->staging, 0);
}
ret = true;
error:
pl_d3d11_timer_end(gpu, params->timer);
pl_d3d11_flush_message_queue(ctx, "After texture download");
pl_free(slices);
return ret;
}