blob: bfc924b145e6af3778b5c333eccad9ab047eaf4d [file] [log] [blame]
// Copyright (C) 2016 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package vulkan
import (
"fmt"
"strings"
"android.googlesource.com/platform/tools/gpu/framework/device"
"android.googlesource.com/platform/tools/gpu/framework/log"
"android.googlesource.com/platform/tools/gpu/framework/task"
"android.googlesource.com/platform/tools/gpu/gapid/atom"
"android.googlesource.com/platform/tools/gpu/gapid/atom/transform"
"android.googlesource.com/platform/tools/gpu/gapid/config"
"android.googlesource.com/platform/tools/gpu/gapid/database"
"android.googlesource.com/platform/tools/gpu/gapid/gfxapi"
"android.googlesource.com/platform/tools/gpu/gapid/gfxapi/state"
"android.googlesource.com/platform/tools/gpu/gapid/image"
"android.googlesource.com/platform/tools/gpu/gapid/memory"
"android.googlesource.com/platform/tools/gpu/gapid/replay"
)
var (
// Interface compliance tests
_ = replay.QueryIssues(api{})
_ = replay.QueryFramebufferAttachment(api{})
)
// makeAttachementReadable is a transformation marking all color/depth/stencil attachment
// images created via vkCreateImage atoms as readable (by patching the transfer src bit).
type makeAttachementReadable struct {
state *gfxapi.State
database database.Database
}
// drawConfig is a replay.Config used by colorBufferRequest and
// depthBufferRequests.
type drawConfig struct {
}
type imgRes struct {
img *image.Image // The image data.
err error // The error that occurred generating the image.
}
// framebufferRequest requests a postback of a framebuffer's attachment.
type framebufferRequest struct {
after atom.ID
width, height uint32
attachment gfxapi.FramebufferAttachment
out chan imgRes
wireframeOverlay bool
}
// color/depth/stencil attachment bit.
func patchImageUsage(usage VkImageUsageFlags) (VkImageUsageFlags, bool) {
hasBit := func(flag VkImageUsageFlags, bit VkImageUsageFlagBits) bool {
return (uint32(flag) & uint32(bit)) == uint32(bit)
}
if hasBit(usage, VkImageUsageFlagBits_VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) ||
hasBit(usage, VkImageUsageFlagBits_VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) {
return VkImageUsageFlags(uint32(usage) | uint32(VkImageUsageFlagBits_VK_IMAGE_USAGE_TRANSFER_SRC_BIT)), true
}
return usage, false
}
func (t *makeAttachementReadable) Transform(ctx log.Context, id atom.ID, a atom.Atom, out atom.Writer) {
a.Mutate(ctx, t.state, t.database, nil)
if image, ok := a.(*VkCreateImage); ok {
pinfo := image.PCreateInfo
info := pinfo.Read(ctx, image, t.state, t.database, nil)
if newUsage, changed := patchImageUsage(info.Usage); changed {
device := image.Device
palloc := memory.Pointer(image.PAllocator)
pimage := memory.Pointer(image.PImage)
result := image.Result
info.Usage = newUsage
newInfo := atom.Must(atom.AllocData(ctx, t.state, t.database, info))
newAtom := NewVkCreateImage(device, newInfo.Ptr(), palloc, pimage, result)
// Carry all non-observation extras through.
for _, e := range image.Extras().All() {
if _, ok := e.(*atom.Observations); !ok {
newAtom.Extras().Add(e)
}
}
// Carry observations through. We cannot merge these code with the
// above code for handling extras together since we'd like to change
// the observations, which are slices.
observations := image.Extras().Observations()
for _, r := range observations.Reads {
// TODO: filter out the old VkImageCreateInfo. That should be done via
// creating new observations for data we are interested from t.state.
newAtom.AddRead(r.Range, r.ID)
}
// Use our new VkImageCreateInfo.
newAtom.AddRead(newInfo.Data())
for _, w := range observations.Writes {
newAtom.AddWrite(w.Range, w.ID)
}
out.Write(ctx, id, newAtom)
return
}
} else if swapchain, ok := a.(*VkCreateSwapchainKHR); ok {
pinfo := swapchain.PCreateInfo
info := pinfo.Read(ctx, swapchain, t.state, t.database, nil)
if newUsage, changed := patchImageUsage(info.ImageUsage); changed {
device := swapchain.Device
palloc := memory.Pointer(swapchain.PAllocator)
pswapchain := memory.Pointer(swapchain.PSwapchain)
result := swapchain.Result
info.ImageUsage = newUsage
newInfo := atom.Must(atom.AllocData(ctx, t.state, t.database, info))
newAtom := NewVkCreateSwapchainKHR(device, newInfo.Ptr(), palloc, pswapchain, result)
for _, e := range swapchain.Extras().All() {
if _, ok := e.(*atom.Observations); !ok {
newAtom.Extras().Add(e)
}
}
observations := swapchain.Extras().Observations()
for _, r := range observations.Reads {
// TODO: filter out the old VkSwapchainCreateInfoKHR. That should be done via
// creating new observations for data we are interested from t.state.
newAtom.AddRead(r.Range, r.ID)
}
newAtom.AddRead(newInfo.Data())
for _, w := range observations.Writes {
newAtom.AddWrite(w.Range, w.ID)
}
out.Write(ctx, id, newAtom)
return
}
} else if createRenderPass, ok := a.(*VkCreateRenderPass); ok {
ctx.Enter("path transformation, create render branch")
pInfo := createRenderPass.PCreateInfo
info := pInfo.Read(ctx, createRenderPass, t.state, t.database, nil)
pAttachments := info.PAttachments
attachments := pAttachments.Slice(uint64(0), uint64(info.AttachmentCount), t.state).Read(ctx, createRenderPass, t.state, t.database, nil)
changed := false
for i, _ := range attachments {
if attachments[i].StoreOp == VkAttachmentStoreOp_VK_ATTACHMENT_STORE_OP_DONT_CARE {
changed = true
attachments[i].StoreOp = VkAttachmentStoreOp_VK_ATTACHMENT_STORE_OP_STORE
}
}
// Returns if no attachment description needs to be changed
ctx.Error().Log("after checking")
if !changed {
out.Write(ctx, id, a)
return
}
ctx.Error().Log("changed")
// Build new attachments data, new create info and new atom
newAttachments := atom.Must(atom.AllocData(ctx, t.state, t.database, attachments))
info.PAttachments = VkAttachmentDescriptionᶜᵖ(newAttachments.Ptr())
newInfo := atom.Must(atom.AllocData(ctx, t.state, t.database, info))
newAtom := NewVkCreateRenderPass(createRenderPass.Device,
newInfo.Ptr(),
memory.Pointer(createRenderPass.PAllocator),
memory.Pointer(createRenderPass.PRenderPass),
createRenderPass.Result)
// Add back the extras and read/write observations
for _, e := range createRenderPass.Extras().All() {
if _, ok := e.(*atom.Observations); !ok {
newAtom.Extras().Add(e)
}
}
for _, r := range createRenderPass.Extras().Observations().Reads {
newAtom.AddRead(r.Range, r.ID)
}
newAtom.AddRead(newInfo.Data()).AddRead(newAttachments.Data())
for _, w := range createRenderPass.Extras().Observations().Writes {
newAtom.AddWrite(w.Range, w.ID)
}
out.Write(ctx, id, newAtom)
return
}
out.Write(ctx, id, a)
}
func (t *makeAttachementReadable) Flush(ctx log.Context, out atom.Writer) {}
// destroyResourceAtEOS is a transformation that destroys all active
// resources at the end of stream.
type destroyResourcesAtEOS struct {
state *gfxapi.State
database database.Database
}
func (t *destroyResourcesAtEOS) Transform(ctx log.Context, id atom.ID, a atom.Atom, out atom.Writer) {
a.Mutate(ctx, t.state, t.database, nil)
out.Write(ctx, id, a)
}
func (t *destroyResourcesAtEOS) Flush(ctx log.Context, out atom.Writer) {
so := getStateObject(t.state)
id := atom.NoID
// TODO: use the correct pAllocator once we handle it.
p := memory.Nullptr
// Wait all queues in all devices to finish their jobs first.
for handle, _ := range so.Devices {
out.Write(ctx, id, NewVkDeviceWaitIdle(handle, VkResult_VK_SUCCESS))
}
// Synchronization primitives.
for handle, object := range so.Events {
out.Write(ctx, id, NewVkDestroyEvent(object.Device, handle, p))
}
for handle, object := range so.Fences {
out.Write(ctx, id, NewVkDestroyFence(object.Device, handle, p))
}
for handle, object := range so.Semaphores {
out.Write(ctx, id, NewVkDestroySemaphore(object.Device, handle, p))
}
// Framebuffers, samplers.
for handle, object := range so.Framebuffers {
out.Write(ctx, id, NewVkDestroyFramebuffer(object.Device, handle, p))
}
for handle, object := range so.Samplers {
out.Write(ctx, id, NewVkDestroySampler(object.Device, handle, p))
}
for handle, object := range so.ImageViews {
out.Write(ctx, id, NewVkDestroyImageView(object.Device, handle, p))
}
// Buffers.
for handle, object := range so.BufferViews {
out.Write(ctx, id, NewVkDestroyBufferView(object.Device, handle, p))
}
for handle, object := range so.Buffers {
out.Write(ctx, id, NewVkDestroyBuffer(object.Device, handle, p))
}
// Descriptor sets.
for handle, object := range so.DescriptorPools {
out.Write(ctx, id, NewVkDestroyDescriptorPool(object.Device, handle, p))
}
for handle, object := range so.DescriptorSetLayouts {
out.Write(ctx, id, NewVkDestroyDescriptorSetLayout(object.Device, handle, p))
}
// Shader modules.
for handle, object := range so.ShaderModules {
out.Write(ctx, id, NewVkDestroyShaderModule(object.Device, handle, p))
}
// Pipelines.
for handle, object := range so.Pipelines {
out.Write(ctx, id, NewVkDestroyPipeline(object.Device, handle, p))
}
for handle, object := range so.PipelineLayouts {
out.Write(ctx, id, NewVkDestroyPipelineLayout(object.Device, handle, p))
}
for handle, object := range so.PipelineCaches {
out.Write(ctx, id, NewVkDestroyPipelineCache(object.Device, handle, p))
}
// Render passes.
for handle, object := range so.RenderPasses {
out.Write(ctx, id, NewVkDestroyRenderPass(object.Device, handle, p))
}
for handle, object := range so.QueryPools {
out.Write(ctx, id, NewVkDestroyQueryPool(object.Device, handle, p))
}
// Command buffers.
for handle, object := range so.CommandPools {
out.Write(ctx, id, NewVkDestroyCommandPool(object.Device, handle, p))
}
// Swapchains.
for handle, object := range so.Swapchains {
out.Write(ctx, id, NewVkDestroySwapchainKHR(object.Device, handle, p))
}
// Memories.
for handle, object := range so.DeviceMemories {
out.Write(ctx, id, NewVkFreeMemory(object.Device, handle, p))
}
// Note: so.Images also contains Swapchain images. We do not want
// to delete those, as that must be handled by VkDestroySwapchainKHR
for handle, object := range so.Images {
if !object.IsSwapchainImage {
out.Write(ctx, id, NewVkDestroyImage(object.Device, handle, p))
}
}
// Devices.
for handle, _ := range so.Devices {
out.Write(ctx, id, NewVkDestroyDevice(handle, p))
}
// Surfaces.
for handle, object := range so.Surfaces {
out.Write(ctx, id, NewVkDestroySurfaceKHR(object.Instance, handle, p))
}
// Instances.
for handle, _ := range so.Instances {
out.Write(ctx, id, NewVkDestroyInstance(handle, p))
}
}
// issuesConfig is a replay.Config used by issuesRequests.
type issuesConfig struct{}
// issuesRequest requests all issues found during replay to be reported to out.
type issuesRequest struct {
out chan<- replay.Issue
}
func (a api) Replay(
ctx log.Context,
intent replay.Intent,
cfg replay.Config,
requests []replay.Request,
device *device.Information,
atoms atom.List,
out atom.Writer,
d database.Database) error {
transforms := atom.Transforms{}
transforms.Add(&makeAttachementReadable{state: state.New(ctx), database: d})
readFramebuffer := newReadFramebuffer(ctx, d)
injector := &transform.Injector{}
// Gathers and reports any issues found.
var issues *findIssues
// Terminate after all atoms of interest.
earlyTerminator := &transform.EarlyTerminator{}
for _, req := range requests {
switch req := req.(type) {
case issuesRequest:
if issues == nil {
issues = &findIssues{}
}
issues.reportTo(req.out)
case framebufferRequest:
earlyTerminator.Add(req.after)
switch req.attachment {
case gfxapi.FramebufferAttachment_Depth:
return fmt.Errorf("Depth attachments are not currently supported")
case gfxapi.FramebufferAttachment_Stencil:
return fmt.Errorf("Stencil attachments are not currently supported")
default:
idx := uint32(req.attachment - gfxapi.FramebufferAttachment_Color0)
readFramebuffer.Color(req.after, req.width, req.height, idx, req.out)
}
}
}
if issues != nil {
transforms.Add(issues) // Issue reporting required.
} else {
transforms.Add(earlyTerminator)
}
// Cleanup
transforms.Add(readFramebuffer, injector)
transforms.Add(&destroyResourcesAtEOS{state: state.New(ctx), database: d})
if config.DebugReplay {
ctx.Info().Logf("Replaying %d atoms using transform chain:", len(atoms.Atoms))
for i, t := range transforms {
ctx.Info().Logf("(%d) %#v", i, t)
}
}
if config.LogTransformsToFile {
newTransforms := atom.Transforms{}
newTransforms.Add(transform.NewFileLog(ctx, d, "0_original_atoms"))
for i, t := range transforms {
var name string
if n, ok := t.(interface {
Name() string
}); ok {
name = n.Name()
} else {
name = strings.Replace(fmt.Sprintf("%T", t), "*", "", -1)
}
newTransforms.Add(t, transform.NewFileLog(ctx, d, fmt.Sprintf("%v_atoms_after_%v", i+1, name)))
}
transforms = newTransforms
}
return catchPanics(ctx, func() { transforms.Transform(ctx, atoms, out) })
}
func catchPanics(ctx log.Context, do func()) (err error) {
defer func() {
if e := recover(); e != nil {
err = ctx.AsErrorf("Panic raised: %v", e)
}
}()
do()
return nil
}
func (a api) QueryFramebufferAttachment(
ctx log.Context,
intent replay.Intent,
mgr *replay.Manager,
after atom.ID,
width, height uint32,
attachment gfxapi.FramebufferAttachment,
wireframeMode replay.WireframeMode) (*image.Image, error) {
c := drawConfig{}
out := make(chan imgRes, 1)
r := framebufferRequest{after: after, width: width, height: height, attachment: attachment, out: out}
if err := mgr.Replay(ctx, intent, c, r, a); err != nil {
return nil, err
}
select {
case res := <-out:
return res.img, res.err
case <-task.ShouldStop(ctx):
return nil, task.StopReason(ctx)
}
}
func (a api) QueryIssues(
ctx log.Context,
intent replay.Intent,
mgr *replay.Manager,
out chan<- replay.Issue) {
c := issuesConfig{}
r := issuesRequest{out: out}
if err := mgr.Replay(ctx, intent, c, r, a); err != nil {
out <- replay.Issue{Atom: atom.NoID, Error: err}
close(out)
}
}