1/*
2 * Copyright 2019 Google LLC
3 * SPDX-License-Identifier: MIT
4 */
5
6#include "vn_cs.h"
7
8#include "vn_instance.h"
9#include "vn_renderer.h"
10
11static void
12vn_cs_encoder_sanity_check(struct vn_cs_encoder *enc)
13{
14   assert(enc->buffer_count <= enc->buffer_max);
15
16   size_t total_committed_size = 0;
17   for (uint32_t i = 0; i < enc->buffer_count; i++)
18      total_committed_size += enc->buffers[i].committed_size;
19   assert(enc->total_committed_size == total_committed_size);
20
21   if (enc->buffer_count) {
22      const struct vn_cs_encoder_buffer *cur_buf =
23         &enc->buffers[enc->buffer_count - 1];
24      assert(cur_buf->base <= enc->cur && enc->cur <= enc->end &&
25             enc->end <= cur_buf->base + enc->current_buffer_size);
26      if (cur_buf->committed_size)
27         assert(enc->cur == enc->end);
28   } else {
29      assert(!enc->current_buffer_size);
30      assert(!enc->cur && !enc->end);
31   }
32}
33
34static void
35vn_cs_encoder_add_buffer(struct vn_cs_encoder *enc,
36                         struct vn_renderer_shmem *shmem,
37                         size_t offset,
38                         void *base,
39                         size_t size)
40{
41   /* add a buffer and make it current */
42   assert(enc->buffer_count < enc->buffer_max);
43   struct vn_cs_encoder_buffer *cur_buf = &enc->buffers[enc->buffer_count++];
44   /* shmem ownership transferred */
45   cur_buf->shmem = shmem;
46   cur_buf->offset = offset;
47   cur_buf->base = base;
48   cur_buf->committed_size = 0;
49
50   /* update the write pointer */
51   enc->cur = base;
52   enc->end = base + size;
53}
54
55static void
56vn_cs_encoder_commit_buffer(struct vn_cs_encoder *enc)
57{
58   assert(enc->buffer_count);
59   struct vn_cs_encoder_buffer *cur_buf =
60      &enc->buffers[enc->buffer_count - 1];
61   const size_t written_size = enc->cur - cur_buf->base;
62   if (cur_buf->committed_size) {
63      assert(cur_buf->committed_size == written_size);
64   } else {
65      cur_buf->committed_size = written_size;
66      enc->total_committed_size += written_size;
67   }
68}
69
70static void
71vn_cs_encoder_gc_buffers(struct vn_cs_encoder *enc)
72{
73   /* free all but the current buffer */
74   assert(enc->buffer_count);
75   struct vn_cs_encoder_buffer *cur_buf =
76      &enc->buffers[enc->buffer_count - 1];
77   for (uint32_t i = 0; i < enc->buffer_count - 1; i++)
78      vn_renderer_shmem_unref(enc->instance->renderer, enc->buffers[i].shmem);
79
80   /* move the current buffer to the beginning, skipping the used part */
81   const size_t used = cur_buf->offset + cur_buf->committed_size;
82   enc->buffer_count = 0;
83   vn_cs_encoder_add_buffer(enc, cur_buf->shmem, used,
84                            cur_buf->base + cur_buf->committed_size,
85                            enc->current_buffer_size - used);
86
87   enc->total_committed_size = 0;
88}
89
90void
91vn_cs_encoder_init_indirect(struct vn_cs_encoder *enc,
92                            struct vn_instance *instance,
93                            size_t min_size)
94{
95   memset(enc, 0, sizeof(*enc));
96   enc->instance = instance;
97   enc->min_buffer_size = min_size;
98   enc->indirect = true;
99}
100
101void
102vn_cs_encoder_fini(struct vn_cs_encoder *enc)
103{
104   if (unlikely(!enc->indirect))
105      return;
106
107   for (uint32_t i = 0; i < enc->buffer_count; i++)
108      vn_renderer_shmem_unref(enc->instance->renderer, enc->buffers[i].shmem);
109   if (enc->buffers)
110      free(enc->buffers);
111}
112
113/**
114 * Reset a cs for reuse.
115 */
116void
117vn_cs_encoder_reset(struct vn_cs_encoder *enc)
118{
119   /* enc->error is sticky */
120   if (likely(enc->buffer_count))
121      vn_cs_encoder_gc_buffers(enc);
122}
123
124static uint32_t
125next_array_size(uint32_t cur_size, uint32_t min_size)
126{
127   const uint32_t next_size = cur_size ? cur_size * 2 : min_size;
128   return next_size > cur_size ? next_size : 0;
129}
130
131static size_t
132next_buffer_size(size_t cur_size, size_t min_size, size_t need)
133{
134   size_t next_size = cur_size ? cur_size * 2 : min_size;
135   while (next_size < need) {
136      next_size *= 2;
137      if (!next_size)
138         return 0;
139   }
140   return next_size;
141}
142
143static bool
144vn_cs_encoder_grow_buffer_array(struct vn_cs_encoder *enc)
145{
146   const uint32_t buf_max = next_array_size(enc->buffer_max, 4);
147   if (!buf_max)
148      return false;
149
150   void *bufs = realloc(enc->buffers, sizeof(*enc->buffers) * buf_max);
151   if (!bufs)
152      return false;
153
154   enc->buffers = bufs;
155   enc->buffer_max = buf_max;
156
157   return true;
158}
159
160/**
161 * Add a new vn_cs_encoder_buffer to a cs.
162 */
163bool
164vn_cs_encoder_reserve_internal(struct vn_cs_encoder *enc, size_t size)
165{
166   if (unlikely(!enc->indirect))
167      return false;
168
169   if (enc->buffer_count >= enc->buffer_max) {
170      if (!vn_cs_encoder_grow_buffer_array(enc))
171         return false;
172      assert(enc->buffer_count < enc->buffer_max);
173   }
174
175   size_t buf_size = 0;
176   if (likely(enc->buffer_count)) {
177      vn_cs_encoder_commit_buffer(enc);
178
179      /* TODO better strategy to grow buffer size */
180      const struct vn_cs_encoder_buffer *cur_buf =
181         &enc->buffers[enc->buffer_count - 1];
182      if (cur_buf->offset)
183         buf_size = next_buffer_size(0, enc->current_buffer_size, size);
184   }
185
186   if (!buf_size) {
187      buf_size = next_buffer_size(enc->current_buffer_size,
188                                  enc->min_buffer_size, size);
189      if (!buf_size)
190         return false;
191   }
192
193   struct vn_renderer_shmem *shmem =
194      vn_renderer_shmem_create(enc->instance->renderer, buf_size);
195   if (!shmem)
196      return false;
197
198   uint32_t roundtrip;
199   VkResult result = vn_instance_submit_roundtrip(enc->instance, &roundtrip);
200   if (result != VK_SUCCESS) {
201      vn_renderer_shmem_unref(enc->instance->renderer, shmem);
202      return false;
203   }
204
205   vn_cs_encoder_add_buffer(enc, shmem, 0, shmem->mmap_ptr, buf_size);
206   enc->current_buffer_size = buf_size;
207   enc->current_buffer_roundtrip = roundtrip;
208
209   vn_cs_encoder_sanity_check(enc);
210
211   return true;
212}
213
214/*
215 * Commit written data.
216 */
217void
218vn_cs_encoder_commit(struct vn_cs_encoder *enc)
219{
220   if (likely(enc->buffer_count)) {
221      vn_cs_encoder_commit_buffer(enc);
222
223      /* trigger the slow path on next vn_cs_encoder_reserve */
224      enc->end = enc->cur;
225   }
226
227   vn_cs_encoder_sanity_check(enc);
228}
229