| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // Represents a source for a draw call, or a log, etc. |
| // |
| class Source { |
| static instances = []; |
| |
| constructor(json) { |
| this.file_ = json.file; |
| this.func_ = json.func; |
| this.line_ = json.line; |
| this.anno_ = json.anno; |
| const index = parseInt(json.index); |
| Source.instances[index] = this; |
| } |
| |
| get file() { return this.file_; } |
| get func() { return this.func_; } |
| get anno() { return this.anno_; } |
| }; |
| |
| // Represents a draw call. |
| // This is currently only used for drawing rects and positional text. |
| |
| class DrawCall { |
| constructor(json) { |
| // e.g. {"drawindex":"44", option":{"alpha":"1.000000","color":"#ffffff"} |
| // ,"pos":"0.000000,763.000000","size":"255x5","source_index":"0", |
| // "thread_id":"123456"} |
| this.sourceIndex_ = parseInt(json.source_index); |
| this.drawIndex_ = parseInt(json.drawindex); |
| this.threadId_ = |
| parseInt(json.thread_id) || DrawFrame.demo_thread.thread_id; |
| this.text = json.text; |
| this.size_ = { |
| width: json.size[0], |
| height: json.size[1], |
| }; |
| |
| this.pos_ = { |
| x: json.pos[0], |
| y: json.pos[1], |
| }; |
| if (json.option) { |
| this.color_ = json.option.color; |
| this.alpha_ = DrawCall.alphaIntToHex(json.option.alpha) |
| } |
| this.buffer_id = json.buff_id || -1; |
| if (json.uv_size && json.uv_pos) { |
| this.uv_size = { |
| width: json.uv_size[0], |
| height: json.uv_size[1], |
| }; |
| this.uv_pos = { |
| x: json.uv_pos[0], |
| y: json.uv_pos[1], |
| }; |
| } |
| else { |
| this.uv_size = { |
| width: 1.0, |
| height: 1.0, |
| }; |
| this.uv_pos = { |
| x: 0.0, |
| y: 0.0, |
| }; |
| } |
| } |
| |
| // Used in conversion of Json. |
| static alphaIntToHex(value) { |
| value = Math.trunc(value); |
| value = Math.max(0, Math.min(value, 255)); |
| return value.toString(16).padStart(2, '0'); |
| } |
| |
| // Used internally to convert from UI filter to hex. |
| static alphaFloatToHex(value) { |
| value = Math.trunc(value * 255); |
| value = Math.max(0, Math.min(value, 255)); |
| return value.toString(16).padStart(2, '0'); |
| } |
| |
| draw(context, buffer_map, threadConfig) { |
| let filter = undefined; |
| const filters = Filter.enabledInstances(); |
| // TODO: multiple filters can match the same draw call. For now, let's just |
| // pick the earliest filter that matches, and let it decide what to do. |
| for (const f of filters) { |
| if (f.matches(Source.instances[this.sourceIndex_])) { |
| filter = f; |
| break; |
| } |
| } |
| |
| var color; |
| var alpha; |
| // If thread drawing is overriding filters. |
| if (threadConfig.overrideFilters) { |
| color = threadConfig.threadColor; |
| alpha = threadConfig.threadAlpha; |
| } |
| // Otherwise, follow filter draw options. |
| else { |
| // No filters match this draw. So skip. |
| if (!filter) return; |
| if (!filter.shouldDraw) return; |
| |
| color = (filter && filter.drawColor) ? filter.drawColor : this.color_ |
| alpha = (filter && filter.fillAlpha) ? |
| DrawCall.alphaFloatToHex(parseFloat(filter.fillAlpha) / 100) : |
| this.alpha_; |
| } |
| |
| if (color && alpha) { |
| context.fillStyle = color + alpha; |
| context.fillRect(this.pos_.x, |
| this.pos_.y, |
| this.size_.width, |
| this.size_.height); |
| } |
| |
| context.strokeStyle = color; |
| context.strokeRect(this.pos_.x, |
| this.pos_.y, |
| this.size_.width, |
| this.size_.height); |
| var buff_id = this.buffer_id.toString(); |
| if(buffer_map[buff_id]) { |
| var buff_width = buffer_map[buff_id].width; |
| var buff_height = buffer_map[buff_id].height; |
| context.drawImage(buffer_map[buff_id], |
| this.uv_pos.x * buff_width, |
| this.uv_pos.y * buff_height, |
| this.uv_size.width * buff_width, |
| this.uv_size.height * buff_height, |
| this.pos_.x, |
| this.pos_.y, |
| this.size_.width, |
| this.size_.height); |
| } |
| } |
| }; |
| |
| |
| // Represents a filter for draw calls. A filter specifies a selector (e.g. |
| // filename, and/or function name), and the action to take (e.g. skip draw, or |
| // color to use for draw, etc.) if the filter matches. |
| class Filter { |
| static instances = []; |
| |
| constructor(enabled, selector, action, index) { |
| this.selector_ = { |
| filename: selector.filename, |
| func: selector.func, |
| anno: selector.anno, |
| }; |
| |
| // XXX: If there are multiple selectors that apply to the same draw, then |
| // I guess the newest filter will take effect. |
| this.action_ = { |
| skipDraw: action.skipDraw, |
| color: action.color, |
| alpha: action.alpha, |
| }; |
| |
| this.enabled_ = enabled; |
| |
| if (index === undefined) { |
| Filter.instances.push(this); |
| this.index_ = Filter.instances.length - 1; |
| } |
| else { |
| Filter.instances[index] = this; |
| this.index_ = index; |
| } |
| } |
| |
| get enabled() { return this.enabled_; } |
| set enabled(e) { this.enabled_ = e; } |
| |
| get file() { return this.selector_.filename || ""; } |
| get func() { return this.selector_.func || ""; } |
| get anno() { return this.selector_.anno || "" }; |
| |
| get shouldDraw() { return !this.action_.skipDraw; } |
| // undefined if using caller color |
| get drawColor() { return this.action_.color; } |
| // undefined if using caller alpha |
| get fillAlpha() { return this.action_.alpha; } |
| |
| get index() { return this.index_; } |
| |
| get streamFilter() { |
| return { |
| selector: { |
| file: this.selector_.filename, |
| func: this.selector_.func, |
| anno: this.selector_.anno |
| }, |
| active: !this.action_.skipDraw, |
| enabled: this.enabled_ |
| } |
| } |
| |
| matches(source) { |
| if (!(source instanceof Source)) return false; |
| if (!this.enabled) return false; |
| |
| if (this.selector_.filename) { |
| const m = source.file.search(this.selector_.filename); |
| if (m == -1) return false; |
| } |
| |
| if (this.selector_.func) { |
| const m = source.func.search(this.selector_.func); |
| if (m == -1) return false; |
| } |
| |
| if (this.selector_.anno) { |
| const m = source.anno.search(this.selector_.anno); |
| if (m == -1) return false; |
| } |
| |
| return true; |
| } |
| |
| createUIString() { |
| let str = ''; |
| if (this.selector_.filename) { |
| const parts = this.selector_.filename.split('/'); |
| str += ` <i class="material-icons-outlined md-18"> |
| text_snippet</i>${parts[parts.length - 1]}`; |
| } |
| if (this.selector_.func) { |
| str += ` <i class="material-icons-outlined md-18"> |
| code</i>${this.selector_.func}`; |
| } |
| if (this.selector_.anno) { |
| str += ` <i class="material-icons-outlined md-18"> |
| message</i>${this.selector_.anno}`; |
| } |
| return str; |
| } |
| |
| static enabledInstances() { |
| return Filter.instances.filter(f => f.enabled); |
| } |
| |
| static getFilter(index) { |
| return Filter.instances[index]; |
| } |
| |
| static swapFilters(indexA, indexB) { |
| var filterA = Filter.instances[indexA]; |
| var filterB = Filter.instances[indexB]; |
| filterA.index_ = indexB; |
| filterB.index_ = indexA; |
| Filter.instances[indexB] = filterA; |
| Filter.instances[indexA] = filterB; |
| } |
| |
| static deleteFilter(index) { |
| Filter.instances.splice(index, 1); |
| for (var i = index; i < Filter.instances.length; i++) { |
| Filter.instances[i].index_ -= 1; |
| } |
| } |
| |
| static sendStreamFilters() { |
| const message = {}; |
| message['method'] = 'VisualDebugger.filterStream'; |
| message['params'] = { |
| filter: { filters: Filter.instances.map((f) => f.streamFilter) } |
| }; |
| Connection.sendMessage(message); |
| } |
| }; |