Welcome to mirror list, hosted at ThFree Co, Russian Federation.

BLI_compute_context.hh « blenlib « blender « source - git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 7422467e400072d00fd481b4c64f3062cb5d7084 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
/* SPDX-License-Identifier: GPL-2.0-or-later */

#pragma once

/** \file
 * \ingroup bli
 *
 * When logging computed values, we generally want to know where the value was computed. For
 * example, geometry nodes logs socket values so that they can be displayed in the ui. For that we
 * can combine the logged value with a `ComputeContext`, which identifies the place where the value
 * was computed.
 *
 * This is not a trivial problem because e.g. just storing storing a pointer to the socket a value
 * belongs to is not enough. That's because the same socket may correspond to many different values
 * when the socket is used in a node group that is used multiple times. In this case, not only does
 * the socket have to be stored but also the entire nested node group path that led to the
 * evaluation of the socket.
 *
 * Storing the entire "context path" for every logged value is not feasible, because that path can
 * become quite long. So that would need much more memory, more compute overhead and makes it
 * complicated to compare if two contexts are the same. If the identifier for a compute context
 * would have a variable size, it would also be much harder to create a map from context to values.
 *
 * The solution implemented below uses the following key ideas:
 * - Every compute context can be hashed to a unique fixed size value (`ComputeContextHash`). While
 *   technically there could be hash collisions, the hashing algorithm has to be chosen to make
 *   that practically impossible. This way an entire context path, possibly consisting of many
 *   nested contexts, is represented by a single value that can be stored easily.
 * - A nested compute context is build as singly linked list, where every compute context has a
 *   pointer to the parent compute context. Note that a link in the other direction is not possible
 *   because the same parent compute context may be used by many different children which possibly
 *   run on different threads.
 */

#include "BLI_array.hh"
#include "BLI_linear_allocator.hh"
#include "BLI_stack.hh"
#include "BLI_string_ref.hh"

namespace blender {

/**
 * A hash that uniquely identifies a specific (non-fixed-size) compute context. The hash has to
 * have enough bits to make collisions practically impossible.
 */
struct ComputeContextHash {
  static constexpr int64_t HashSizeInBytes = 16;
  uint64_t v1 = 0;
  uint64_t v2 = 0;

  uint64_t hash() const
  {
    return v1;
  }

  friend bool operator==(const ComputeContextHash &a, const ComputeContextHash &b)
  {
    return a.v1 == b.v1 && a.v2 == b.v2;
  }

  void mix_in(const void *data, int64_t len);

  friend std::ostream &operator<<(std::ostream &stream, const ComputeContextHash &hash);
};

static_assert(sizeof(ComputeContextHash) == ComputeContextHash::HashSizeInBytes);

/**
 * Identifies the context in which a computation happens. This context can be used to identify
 * values logged during the computation. For more details, see the comment at the top of the file.
 *
 * This class should be subclassed to implement specific contexts.
 */
class ComputeContext {
 private:
  /**
   * Only used for debugging currently.
   */
  const char *static_type_;
  /**
   * Pointer to the context that this context is child of. That allows nesting compute contexts.
   */
  const ComputeContext *parent_ = nullptr;

 protected:
  /**
   * The hash that uniquely identifies this context. It's a combined hash of this context as well
   * as all the parent contexts.
   */
  ComputeContextHash hash_;

 public:
  ComputeContext(const char *static_type, const ComputeContext *parent)
      : static_type_(static_type), parent_(parent)
  {
    if (parent != nullptr) {
      hash_ = parent_->hash_;
    }
  }
  virtual ~ComputeContext() = default;

  const ComputeContextHash &hash() const
  {
    return hash_;
  }

  const char *static_type() const
  {
    return static_type_;
  }

  const ComputeContext *parent() const
  {
    return parent_;
  }

  /**
   * Print the entire nested context stack.
   */
  void print_stack(std::ostream &stream, StringRef name) const;

  /**
   * Print information about this specific context. This has to be implemented by each subclass.
   */
  virtual void print_current_in_line(std::ostream &stream) const = 0;

  friend std::ostream &operator<<(std::ostream &stream, const ComputeContext &compute_context);
};

/**
 * Utility class to build a context stack in one place. This is typically used to get the hash that
 * corresponds to a specific nested compute context, in order to look up corresponding logged
 * values.
 */
class ComputeContextBuilder {
 private:
  LinearAllocator<> allocator_;
  Stack<destruct_ptr<ComputeContext>> contexts_;

 public:
  bool is_empty() const
  {
    return contexts_.is_empty();
  }

  const ComputeContext *current() const
  {
    if (contexts_.is_empty()) {
      return nullptr;
    }
    return contexts_.peek().get();
  }

  const ComputeContextHash hash() const
  {
    BLI_assert(!contexts_.is_empty());
    return this->current()->hash();
  }

  template<typename T, typename... Args> void push(Args &&...args)
  {
    const ComputeContext *current = this->current();
    destruct_ptr<T> context = allocator_.construct<T>(current, std::forward<Args>(args)...);
    contexts_.push(std::move(context));
  }

  void pop()
  {
    contexts_.pop();
  }
};

}  // namespace blender