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