Merge branch 'feat/10090-graphics-cache-dir' of https://github.com/Mekann2904/kitty

This commit is contained in:
Kovid Goyal 2026-06-19 10:16:10 +05:30
commit a2104d3755
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
10 changed files with 92 additions and 21 deletions

View file

@ -1051,6 +1051,16 @@ Key Value Default Description
``o`` Single character. ``null`` The type of data compression. ``o`` Single character. ``null`` The type of data compression.
``only z`` ``only z``
``m`` zero or one ``0`` Whether there is more chunked data available. ``m`` zero or one ``0`` Whether there is more chunked data available.
``N`` bitmask ``0`` Usage hints from the client to the terminal about the intended use of
the image. Only one hint is currently defined, the ``1`` bit which means
*transient*. The terminal is free to assume that an image with this hint
will be used for only a short time, and so may, for example, evict its
data before other images when the image is soft deleted, has no visible
placements and the terminal is under storage pressure, or skip writing
its data to disk. The terminal is also free to ignore the hint. If an
animation frame with the *transient* hint is composited onto another
frame, and any of the involved frames have the hint, the resulting
composited frame also has the hint.
**Keys for image display** **Keys for image display**
----------------------------------------------------------- -----------------------------------------------------------

View file

@ -313,6 +313,7 @@ def parsers() -> None:
'U': ('unicode_placement', 'uint'), 'U': ('unicode_placement', 'uint'),
'P': ('parent_id', 'uint'), 'P': ('parent_id', 'uint'),
'Q': ('parent_placement_id', 'uint'), 'Q': ('parent_placement_id', 'uint'),
'N': ('usage_hints', 'uint'),
'H': ('offset_from_parent_x', 'int'), 'H': ('offset_from_parent_x', 'int'),
'V': ('offset_from_parent_y', 'int'), 'V': ('offset_from_parent_y', 'int'),
} }

View file

@ -33,7 +33,7 @@ typedef struct CacheKey {
typedef struct { typedef struct {
uint8_t *data; uint8_t *data;
size_t data_sz; size_t data_sz;
bool written_to_disk, uses_encryption; bool written_to_disk, uses_encryption, memory_only;
off_t pos_in_cache_file; off_t pos_in_cache_file;
uint8_t encryption_key[64]; uint8_t encryption_key[64];
} CacheValue; } CacheValue;
@ -593,7 +593,7 @@ create_cache_entry(void) {
} }
bool bool
add_to_disk_cache(PyObject *self_, const void *key, size_t key_sz, const void *data, size_t data_sz) { add_to_disk_cache(PyObject *self_, const void *key, size_t key_sz, const void *data, size_t data_sz, bool memory_only) {
DiskCache *self = (DiskCache*)self_; DiskCache *self = (DiskCache*)self_;
if (!ensure_state(self)) return false; if (!ensure_state(self)) return false;
if (key_sz > MAX_KEY_SIZE) { PyErr_SetString(PyExc_KeyError, "cache key is too long"); return false; } if (key_sz > MAX_KEY_SIZE) { PyErr_SetString(PyExc_KeyError, "cache key is too long"); return false; }
@ -618,11 +618,14 @@ add_to_disk_cache(PyObject *self_, const void *key, size_t key_sz, const void *d
if (s->data) free(s->data); if (s->data) free(s->data);
} }
s->data = copied_data; s->data_sz = data_sz; copied_data = NULL; s->data = copied_data; s->data_sz = data_sz; copied_data = NULL;
s->memory_only = memory_only;
s->written_to_disk = memory_only;
if (memory_only) s->pos_in_cache_file = -1;
self->total_size += s->data_sz; self->total_size += s->data_sz;
end: end:
mutex(unlock); mutex(unlock);
if (PyErr_Occurred()) return false; if (PyErr_Occurred()) return false;
wakeup_write_loop(self); if (!memory_only) wakeup_write_loop(self);
return true; return true;
} }
@ -740,7 +743,7 @@ disk_cache_clear_from_ram(PyObject *self_, bool(matches)(void*, void *key, unsig
mutex(lock); mutex(lock);
cache_map_for_loop(i) { cache_map_for_loop(i) {
CacheValue *s = i.data->val; CacheValue *s = i.data->val;
if (s->written_to_disk && s->data && matches(data, i.data->key.hash_key, i.data->key.hash_keylen)) { if (s->written_to_disk && !s->memory_only && s->data && matches(data, i.data->key.hash_key, i.data->key.hash_keylen)) {
free(s->data); s->data = NULL; free(s->data); s->data = NULL;
ans++; ans++;
} }
@ -848,7 +851,7 @@ add(PyObject *self, PyObject *args) {
const char *key, *data; const char *key, *data;
Py_ssize_t keylen, datalen; Py_ssize_t keylen, datalen;
PA("y#y#", &key, &keylen, &data, &datalen); PA("y#y#", &key, &keylen, &data, &datalen);
if (!add_to_disk_cache(self, key, keylen, data, datalen)) return NULL; if (!add_to_disk_cache(self, key, keylen, data, datalen, false)) return NULL;
Py_RETURN_NONE; Py_RETURN_NONE;
} }

View file

@ -9,7 +9,7 @@
#include "data-types.h" #include "data-types.h"
PyObject* create_disk_cache(void); PyObject* create_disk_cache(void);
bool add_to_disk_cache(PyObject *self, const void *key, size_t key_sz, const void *data, size_t data_sz); bool add_to_disk_cache(PyObject *self, const void *key, size_t key_sz, const void *data, size_t data_sz, bool memory_only);
bool remove_from_disk_cache(PyObject *self_, const void *key, size_t key_sz); bool remove_from_disk_cache(PyObject *self_, const void *key, size_t key_sz);
void* read_from_disk_cache(PyObject *self_, const void *key, size_t key_sz, void*(allocator)(void*, size_t), void*, bool); void* read_from_disk_cache(PyObject *self_, const void *key, size_t key_sz, void*(allocator)(void*, size_t), void*, bool);
PyObject* read_from_disk_cache_python(PyObject *self_, const void *key, size_t key_sz, bool); PyObject* read_from_disk_cache_python(PyObject *self_, const void *key, size_t key_sz, bool);

View file

@ -41,9 +41,9 @@ cache_key(const ImageAndFrame x, char *key) {
#define CK(x) key, cache_key(x, key) #define CK(x) key, cache_key(x, key)
static bool static bool
add_to_cache(GraphicsManager *self, const ImageAndFrame x, const void *data, const size_t sz) { add_to_cache(GraphicsManager *self, const ImageAndFrame x, const void *data, const size_t sz, bool memory_only) {
char key[CACHE_KEY_BUFFER_SIZE]; char key[CACHE_KEY_BUFFER_SIZE];
return add_to_disk_cache(self->disk_cache, CK(x), data, sz); return add_to_disk_cache(self->disk_cache, CK(x), data, sz, memory_only);
} }
static bool static bool
@ -768,11 +768,12 @@ handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_
.is_opaque = self->currently_loading.is_opaque, .is_opaque = self->currently_loading.is_opaque,
.is_4byte_aligned = self->currently_loading.is_4byte_aligned, .is_4byte_aligned = self->currently_loading.is_4byte_aligned,
.width = img->width, .height = img->height, .width = img->width, .height = img->height,
.transient = (g->usage_hints & GRAPHICS_USAGE_HINT_TRANSIENT) != 0,
}; };
if (!is_query) { if (!is_query) {
if (!add_to_cache(self, (const ImageAndFrame){.image_id = img->internal_id, .frame_id=img->root_frame.id}, self->currently_loading.data, self->currently_loading.data_sz)) { if (!add_to_cache(self, (const ImageAndFrame){.image_id = img->internal_id, .frame_id=img->root_frame.id}, self->currently_loading.data, self->currently_loading.data_sz, img->root_frame.transient)) {
if (PyErr_Occurred()) PyErr_Print(); if (PyErr_Occurred()) PyErr_Print();
ABRT("ENOSPC", "Failed to store image data in disk cache"); ABRT("ENOSPC", "Failed to store image data in cache");
} }
upload_to_gpu(self, img, img->root_frame.is_opaque, img->root_frame.is_4byte_aligned, self->currently_loading.data); upload_to_gpu(self, img, img->root_frame.is_opaque, img->root_frame.is_4byte_aligned, self->currently_loading.data);
self->used_storage += required_sz; self->used_storage += required_sz;
@ -1354,7 +1355,7 @@ change_gap(Image *img, Frame *f, int32_t gap) {
typedef struct { typedef struct {
uint8_t *buf; uint8_t *buf;
bool is_4byte_aligned, is_opaque; bool is_4byte_aligned, is_opaque, transient;
} CoalescedFrameData; } CoalescedFrameData;
static void static void
@ -1450,6 +1451,7 @@ compose(const ComposeData d, uint8_t *under_data, const uint8_t *over_data) {
static CoalescedFrameData static CoalescedFrameData
get_coalesced_frame_data_standalone(const Image *img, const Frame *f, uint8_t *frame_data) { get_coalesced_frame_data_standalone(const Image *img, const Frame *f, uint8_t *frame_data) {
CoalescedFrameData ans = {0}; CoalescedFrameData ans = {0};
ans.transient = f->transient;
bool is_full_frame = f->width == img->width && f->height == img->height && !f->x && !f->y; bool is_full_frame = f->width == img->width && f->height == img->height && !f->x && !f->y;
if (is_full_frame) { if (is_full_frame) {
ans.buf = frame_data; ans.buf = frame_data;
@ -1513,6 +1515,7 @@ get_coalesced_frame_data_impl(GraphicsManager *self, Image *img, const Frame *f,
}; };
compose(d, base_data.buf, frame_data); compose(d, base_data.buf, frame_data);
free(frame_data); free(frame_data);
base_data.transient = base_data.transient || f->transient;
return base_data; return base_data;
} }
@ -1553,6 +1556,17 @@ reference_chain_too_large(Image *img, const Frame *frame) {
return num >= 5 || drawn_area >= limit; return num >= 5 || drawn_area >= limit;
} }
static bool
frame_chain_is_transient(Image *img, const Frame *frame) {
// matches the recursion depth limit in get_coalesced_frame_data_impl
unsigned num = 0;
while (frame) {
if (frame->transient) return true;
if (!frame->base_frame_id || ++num > 32 || !(frame = frame_for_id(img, frame->base_frame_id))) break;
}
return false;
}
static Image* static Image*
handle_animation_frame_load_command(GraphicsManager *self, GraphicsCommand *g, Image *img, const uint8_t *payload, bool *is_dirty) { handle_animation_frame_load_command(GraphicsManager *self, GraphicsCommand *g, Image *img, const uint8_t *payload, bool *is_dirty) {
uint32_t frame_number = g->frame_number, fmt = g->format ? g->format : RGBA; uint32_t frame_number = g->frame_number, fmt = g->format ? g->format : RGBA;
@ -1595,6 +1609,7 @@ handle_animation_frame_load_command(GraphicsManager *self, GraphicsCommand *g, I
.alpha_blend = g->compose_mode != 1 && !load_data->is_opaque, .alpha_blend = g->compose_mode != 1 && !load_data->is_opaque,
.gap = g->gap > 0 ? g->gap : (g->gap < 0) ? 0 : DEFAULT_GAP, .gap = g->gap > 0 ? g->gap : (g->gap < 0) ? 0 : DEFAULT_GAP,
.bgcolor = g->bgcolor, .bgcolor = g->bgcolor,
.transient = (g->usage_hints & GRAPHICS_USAGE_HINT_TRANSIENT) != 0,
}; };
Frame *frame; Frame *frame;
if (is_new_frame) { if (is_new_frame) {
@ -1630,12 +1645,14 @@ handle_animation_frame_load_command(GraphicsManager *self, GraphicsCommand *g, I
transmitted_frame.x = 0; transmitted_frame.y = 0; transmitted_frame.x = 0; transmitted_frame.y = 0;
transmitted_frame.is_4byte_aligned = cfd.is_4byte_aligned; transmitted_frame.is_4byte_aligned = cfd.is_4byte_aligned;
transmitted_frame.is_opaque = cfd.is_opaque; transmitted_frame.is_opaque = cfd.is_opaque;
transmitted_frame.transient = transmitted_frame.transient || cfd.transient;
} else { } else {
transmitted_frame.base_frame_id = other_frame->id; transmitted_frame.base_frame_id = other_frame->id;
transmitted_frame.transient = transmitted_frame.transient || frame_chain_is_transient(img, other_frame);
} }
} }
*frame = transmitted_frame; *frame = transmitted_frame;
if (!add_to_cache(self, key, load_data->data, load_data->data_sz)) { if (!add_to_cache(self, key, load_data->data, load_data->data_sz, frame->transient)) {
img->extra_framecnt--; img->extra_framecnt--;
if (PyErr_Occurred()) PyErr_Print(); if (PyErr_Occurred()) PyErr_Print();
ABRT("ENOSPC", "Failed to cache data for image frame"); ABRT("ENOSPC", "Failed to cache data for image frame");
@ -1651,6 +1668,7 @@ handle_animation_frame_load_command(GraphicsManager *self, GraphicsCommand *g, I
if (g->gap != 0) change_gap(img, frame, transmitted_frame.gap); if (g->gap != 0) change_gap(img, frame, transmitted_frame.gap);
CoalescedFrameData cfd = get_coalesced_frame_data(self, img, frame); CoalescedFrameData cfd = get_coalesced_frame_data(self, img, frame);
if (!cfd.buf) ABRT("EINVAL", "No data associated with frame number: %u", frame_number); if (!cfd.buf) ABRT("EINVAL", "No data associated with frame number: %u", frame_number);
frame->transient = cfd.transient || transmitted_frame.transient;
frame->alpha_blend = false; frame->base_frame_id = 0; frame->bgcolor = 0; frame->alpha_blend = false; frame->base_frame_id = 0; frame->bgcolor = 0;
frame->is_opaque = cfd.is_opaque; frame->is_4byte_aligned = cfd.is_4byte_aligned; frame->is_opaque = cfd.is_opaque; frame->is_4byte_aligned = cfd.is_4byte_aligned;
frame->x = 0; frame->y = 0; frame->width = img->width; frame->height = img->height; frame->x = 0; frame->y = 0; frame->width = img->width; frame->height = img->height;
@ -1664,7 +1682,7 @@ handle_animation_frame_load_command(GraphicsManager *self, GraphicsCommand *g, I
}; };
compose(d, cfd.buf, load_data->data); compose(d, cfd.buf, load_data->data);
const ImageAndFrame key = { .image_id = img->internal_id, .frame_id = frame->id }; const ImageAndFrame key = { .image_id = img->internal_id, .frame_id = frame->id };
bool added = add_to_cache(self, key, cfd.buf, (size_t)bytes_per_pixel * frame->width * frame->height); bool added = add_to_cache(self, key, cfd.buf, (size_t)bytes_per_pixel * frame->width * frame->height, frame->transient);
if (added && frame == current_frame(img)) { if (added && frame == current_frame(img)) {
update_current_frame(self, img, &cfd); update_current_frame(self, img, &cfd);
*is_dirty = true; *is_dirty = true;
@ -1867,10 +1885,13 @@ handle_compose_command(GraphicsManager *self, bool *is_dirty, const GraphicsComm
.stride = img->width .stride = img->width
}; };
compose_rectangles(d, dest_data.buf, src_data.buf); compose_rectangles(d, dest_data.buf, src_data.buf);
bool transient = src_data.transient || dest_data.transient;
const ImageAndFrame key = { .image_id = img->internal_id, .frame_id = dest_frame->id }; const ImageAndFrame key = { .image_id = img->internal_id, .frame_id = dest_frame->id };
if (!add_to_cache(self, key, dest_data.buf, ((size_t)(dest_data.is_opaque ? 3 : 4)) * img->width * img->height)) { if (!add_to_cache(self, key, dest_data.buf, ((size_t)(dest_data.is_opaque ? 3 : 4)) * img->width * img->height, transient)) {
if (PyErr_Occurred()) PyErr_Print(); if (PyErr_Occurred()) PyErr_Print();
set_command_failed_response("ENOSPC", "Failed to store image data in disk cache"); set_command_failed_response("ENOSPC", "Failed to store image data in cache");
} else {
dest_frame->transient = transient;
} }
// frame is now a fully coalesced frame // frame is now a fully coalesced frame
dest_frame->x = 0; dest_frame->y = 0; dest_frame->width = img->width; dest_frame->height = img->height; dest_frame->x = 0; dest_frame->y = 0; dest_frame->width = img->width; dest_frame->height = img->height;

View file

@ -8,9 +8,12 @@
#include "data-types.h" #include "data-types.h"
#include "monotonic.h" #include "monotonic.h"
// Bitmask values for GraphicsCommand.usage_hints
#define GRAPHICS_USAGE_HINT_TRANSIENT 1u
typedef struct { typedef struct {
unsigned char action, transmission_type, compressed, delete_action; unsigned char action, transmission_type, compressed, delete_action;
uint32_t format, more, id, image_number, data_sz, data_offset, placement_id, quiet, parent_id, parent_placement_id; uint32_t format, more, id, image_number, data_sz, data_offset, placement_id, quiet, parent_id, parent_placement_id, usage_hints;
uint32_t width, height, x_offset, y_offset; uint32_t width, height, x_offset, y_offset;
union { uint32_t cursor_movement, compose_mode; }; union { uint32_t cursor_movement, compose_mode; };
union { uint32_t cell_x_offset; }; union { uint32_t cell_x_offset; };
@ -71,7 +74,7 @@ typedef struct {
typedef struct { typedef struct {
uint32_t gap, id, width, height, x, y, base_frame_id, bgcolor; uint32_t gap, id, width, height, x, y, base_frame_id, bgcolor;
bool is_opaque, is_4byte_aligned, alpha_blend; bool is_opaque, is_4byte_aligned, alpha_blend, transient;
} Frame; } Frame;
typedef enum { ANIMATION_STOPPED = 0, ANIMATION_LOADING = 1, ANIMATION_RUNNING = 2} AnimationState; typedef enum { ANIMATION_STOPPED = 0, ANIMATION_LOADING = 1, ANIMATION_RUNNING = 2} AnimationState;

View file

@ -46,6 +46,7 @@ static inline void parse_graphics_code(PS *self, uint8_t *parser_buf,
unicode_placement = 'U', unicode_placement = 'U',
parent_id = 'P', parent_id = 'P',
parent_placement_id = 'Q', parent_placement_id = 'Q',
usage_hints = 'N',
offset_from_parent_x = 'H', offset_from_parent_x = 'H',
offset_from_parent_y = 'V' offset_from_parent_y = 'V'
}; };
@ -141,6 +142,9 @@ static inline void parse_graphics_code(PS *self, uint8_t *parser_buf,
case parent_placement_id: case parent_placement_id:
value_state = UINT; value_state = UINT;
break; break;
case usage_hints:
value_state = UINT;
break;
case offset_from_parent_x: case offset_from_parent_x:
value_state = INT; value_state = INT;
break; break;
@ -299,6 +303,7 @@ static inline void parse_graphics_code(PS *self, uint8_t *parser_buf,
U(unicode_placement); U(unicode_placement);
U(parent_id); U(parent_id);
U(parent_placement_id); U(parent_placement_id);
U(usage_hints);
default: default:
break; break;
} }
@ -359,7 +364,7 @@ static inline void parse_graphics_code(PS *self, uint8_t *parser_buf,
REPORT_VA_COMMAND( REPORT_VA_COMMAND(
"K s {sc sc sc sc sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI " "K s {sc sc sc sc sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI "
"sI sI sI sI si si si ss#}", "sI sI sI sI sI si si si ss#}",
self->window_id, "graphics_command", self->window_id, "graphics_command",
"action", g.action, "delete_action", g.delete_action, "transmission_type", "action", g.action, "delete_action", g.delete_action, "transmission_type",
@ -378,7 +383,8 @@ static inline void parse_graphics_code(PS *self, uint8_t *parser_buf,
"cell_y_offset", (unsigned int)g.cell_y_offset, "cursor_movement", "cell_y_offset", (unsigned int)g.cell_y_offset, "cursor_movement",
(unsigned int)g.cursor_movement, "unicode_placement", (unsigned int)g.cursor_movement, "unicode_placement",
(unsigned int)g.unicode_placement, "parent_id", (unsigned int)g.parent_id, (unsigned int)g.unicode_placement, "parent_id", (unsigned int)g.parent_id,
"parent_placement_id", (unsigned int)g.parent_placement_id, "parent_placement_id", (unsigned int)g.parent_placement_id, "usage_hints",
(unsigned int)g.usage_hints,
"z_index", (int)g.z_index, "offset_from_parent_x", "z_index", (int)g.z_index, "offset_from_parent_x",
(int)g.offset_from_parent_x, "offset_from_parent_y", (int)g.offset_from_parent_x, "offset_from_parent_y",

View file

@ -386,6 +386,20 @@ def test_suppressing_gr_command_responses(self):
self.assertIsNone(li(payload='2' * 12, z=77, m=1, q=2)) self.assertIsNone(li(payload='2' * 12, z=77, m=1, q=2))
self.assertIsNone(li(payload='2' * 12)) self.assertIsNone(li(payload='2' * 12))
def test_transient_graphics_image(self):
s, g, pl, sl = load_helpers(self)
self.assertEqual(g.disk_cache.end_of_data_offset(), 0)
self.ae(pl('abc', s=1, v=1, f=24, N=1), 'OK')
self.assertTrue(g.disk_cache.wait_for_write())
self.assertEqual(g.disk_cache.end_of_data_offset(), 0)
img = g.image_for_client_id(1)
self.assertIsNotNone(img)
self.ae(img['data'], b'abc')
self.ae(pl('def', s=1, v=1, f=24, i=2), 'OK')
self.assertTrue(g.disk_cache.wait_for_write())
self.assertGreater(g.disk_cache.end_of_data_offset(), 0)
def test_load_images(self): def test_load_images(self):
s, g, pl, sl = load_helpers(self) s, g, pl, sl = load_helpers(self)
self.assertEqual(g.disk_cache.total_size, 0) self.assertEqual(g.disk_cache.total_size, 0)

View file

@ -920,7 +920,7 @@ def c(**k):
k.setdefault(f, b'\0') k.setdefault(f, b'\0')
for f in ('format more id data_sz data_offset width height x_offset y_offset data_height data_width cursor_movement' for f in ('format more id data_sz data_offset width height x_offset y_offset data_height data_width cursor_movement'
' num_cells num_lines cell_x_offset cell_y_offset z_index placement_id image_number quiet unicode_placement' ' num_cells num_lines cell_x_offset cell_y_offset z_index placement_id image_number quiet unicode_placement'
' parent_id parent_placement_id offset_from_parent_x offset_from_parent_y' ' parent_id parent_placement_id usage_hints offset_from_parent_x offset_from_parent_y'
).split(): ).split():
k.setdefault(f, 0) k.setdefault(f, 0)
p = k.pop('payload', '') p = k.pop('payload', '')
@ -943,6 +943,7 @@ def e(cmd, err):
t('a=t,t=d,s=100,z=-9', payload='X', action='t', transmission_type='d', data_width=100, z_index=-9) t('a=t,t=d,s=100,z=-9', payload='X', action='t', transmission_type='d', data_width=100, z_index=-9)
t('a=t,t=d,s=100,z=9', payload='payload', action='t', transmission_type='d', data_width=100, z_index=9) t('a=t,t=d,s=100,z=9', payload='payload', action='t', transmission_type='d', data_width=100, z_index=9)
t('a=t,t=d,s=100,z=9,q=2', action='t', transmission_type='d', data_width=100, z_index=9, quiet=2) t('a=t,t=d,s=100,z=9,q=2', action='t', transmission_type='d', data_width=100, z_index=9, quiet=2)
t('N=1', usage_hints=1)
e(',s=1', 'Malformed GraphicsCommand control block, invalid key character: 0x2c') e(',s=1', 'Malformed GraphicsCommand control block, invalid key character: 0x2c')
e('W=1', 'Malformed GraphicsCommand control block, invalid key character: 0x57') e('W=1', 'Malformed GraphicsCommand control block, invalid key character: 0x57')
e('1=1', 'Malformed GraphicsCommand control block, invalid key character: 0x31') e('1=1', 'Malformed GraphicsCommand control block, invalid key character: 0x31')

View file

@ -143,7 +143,7 @@ type GraphicsCommand struct {
d GRT_d d GRT_d
U GRT_U U GRT_U
s, v, S, O, x, y, w, h, X, Y, c, r uint64 s, v, S, O, x, y, w, h, X, Y, c, r, N uint64
i, I, p uint32 i, I, p uint32
@ -176,6 +176,7 @@ func (self *GraphicsCommand) serialize_non_default_fields() (ans []string) {
write_key('U', self.U, null.U) write_key('U', self.U, null.U)
write_key('d', self.d, null.d) write_key('d', self.d, null.d)
write_key('N', self.N, null.N)
write_key('s', self.s, null.s) write_key('s', self.s, null.s)
write_key('v', self.v, null.v) write_key('v', self.v, null.v)
write_key('S', self.S, null.S) write_key('S', self.S, null.S)
@ -376,6 +377,8 @@ func (self *GraphicsCommand) SetString(key byte, value string) (err error) {
err = set_val(&self.U, GRT_U_from_string, value) err = set_val(&self.U, GRT_U_from_string, value)
case 'd': case 'd':
err = set_val(&self.d, GRT_d_from_string, value) err = set_val(&self.d, GRT_d_from_string, value)
case 'N':
err = set_uval(&self.N, value)
case 's': case 's':
err = set_uval(&self.s, value) err = set_uval(&self.s, value)
case 'v': case 'v':
@ -753,6 +756,15 @@ func (self *GraphicsCommand) SetFrameToMakeCurrent(c uint64) *GraphicsCommand {
return self return self
} }
func (self *GraphicsCommand) UsageHints() uint64 {
return self.N
}
func (self *GraphicsCommand) SetUsageHints(hints uint64) *GraphicsCommand {
self.N = hints
return self
}
func (self *GraphicsCommand) ImageId() uint32 { func (self *GraphicsCommand) ImageId() uint32 {
return self.i return self.i
} }