| // 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) |
| } |
| } |